Redis不仅支持string类型的数据,它还支持其他数据结构的数据。就如这节学习的list(列表)数据类型。
一张图理解list(列表)是什么样的:
list类型其实就是一个双向链表。通过push,pop操作从链表的头部或者尾部添加删除元素。
这使得list既可以用作栈,也可以用作队列。Redis的list类型类似于PHP的数组数据类型。
链表、栈、队列这些数据结构不太熟悉的可以看教程的第八章内容。
list类型操作命令:
下面开始做演示:
一 key 为goods(商品)
①为goods添加商品数据
lpush goods nokia htc heimei apple samsung xiaomi lenovo huawei
lpush 命令将一个或多个值插入到列表头部。 当 key 存在但不是列表类型时,返回一个错误。
②查看goods的商品数据
llen goods //获取列表长度 lrange goods 0 7 //查看最前面的8个数据
llen 命令用于返回列表的长度。 如果列表 key 不存在,则 key 被解释为一个空列表,返回 0 。 如果 key 不是列表类型,返回一个错误。
lrange 返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
lindex goods 4
lindex 命令用于通过索引获取列表中的元素。你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
③从尾部添加商品数据
rpush goods vivo oppo
rpush 命令将一个或多个值插入到列表尾部。 当 key 存在但不是列表类型时,返回一个错误。
④删除商品数据
lpop goods rpop goods
lpop 命令用于移除并返回列表的第一个元素。
rpop 命令用于移除并返回列表的最后一个元素。
list类型虽然是双向链表,我们主要把它当成栈或者队列来看,所以删除数据时需要从头或尾一个一个“弹出”。
删除所有的商品数据可以使用上节的(key的)del命令。
⑤裁剪数据
ltrim goods 1 -2
ltrim 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
下标 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
⑥去除重复的数据
lrem goods 1 htc lrem goods -1 htc lrem goods 0 apple
lrem 根据参数 COUNT 的值,移除列表中与参数 VALUE 相等的元素。
COUNT 的值可以是以下几种:
count > 0 : 从表头开始向表尾搜索,移除与 VALUE 相等的元素,数量为 COUNT 。
count < 0 : 从表尾开始向表头搜索,移除与 VALUE 相等的元素,数量为 COUNT 的绝对值。
count = 0 : 移除表中所有与 VALUE 相等的值。
⑦修改指定下标的数据
lset goods 3 xiaohei
lset 通过索引来设置元素的值。
当索引参数超出范围,或对一个空列表进行 LSET 时,返回一个错误。
list类型的应用
list操作起来并不复杂,相比string类型,Redis的list数据类型有哪些用处呢?
string类型仅能做简单的key=》val数据缓存,而list类型除了能做key=》val数据缓存,还可以起到对数据排列的作用。
举一例子(1),一商城网站经常有这样的功能需求:
获得最新的10个商品:select * from goods order by id desc limit 10;
获得最新的10个登录用户信息: select * from user order by logintime desc limit 10;
以上两个sql语句可以实现需求,但是数据多的时候,全部数据都要受到影响,对数据库的负载比较高。
如果通过list链表实现以上功能,就会极大节省各方面资源消耗,可以在list链表中保留最新的10个数据,旧的数据从链表中给去除。每次从链表中直接获取数据即可。
再举一例(2),抢购的功能:
现在购物平台都会有抢购商品的活动,如果平台很火,抢购的瞬间会是网站并发流量的高峰,这会对平台系统是一个大的挑战。
假如一个商品只卖100件,有一万个人同时购买(不是绝对同时,肯定会有先后顺序,只是时间差很细微),这个时候就不能直接操作数据库去实现抢购功能了。
数据库是怎么操作呢?
用户1从数据库获取库存数量,发现库存不为0,生成订单,库存减一。接着用户2、用户3……用户100,可能用户101获取库存量还不为0,然后用户101订单也生成成功了。像这种情况属于高并发下的“超卖”现象,因为操作数据库没有加锁,就会产生超卖。原因假设:用户100获取库存假设是1,它还没来得及把库存修改为0,用户101已从数据库获取库存了,获取的数字是1,所以就产生超卖了。防止超卖我们需要对数据库操作时开启事务加锁,确保数据安全。那问题又会出现,加锁后数据库性能会下降得很厉害,不足以支撑高并发,所以抢购功能不适合直接操作数据库。
数据库的数据毕竟是存在硬盘上,操作数据库就是操作硬盘IO读写。我们都知道在内存对数据读写比硬盘快得多,所以在高并发下直接在内存操作更适合。
在内存里需要用到队列这样的数据结构,把前100名放到队列里,只有队列里的用户才可以购买,这样解决抢购功能要省资源的多。
内存里的队列,就可以用Redis的list类型实现。
继续拓展:
楠神怕有些初学者犯糊涂,Redis的list类型既然与PHP的数组类型颇具相似,抢购功能直接使用PHP的数组来实现可否?
答:
在PHP中,所有的变量都是页面级的,页面执行完变量从内存中清空。别说是A与B两个用户不能共享内存中的同一数组变量,就是A用户同时打开的两个页面也不能共享内存中的同一数组变量。
而Redis是可以让不同用户共享内存中相同key的数据的。如:
A用户通过PHP脚本调用Redis接口,执行命令
lset number 1
B用户通过PHP脚本调用Redis接口,执行命令
lset number 2
A用户通过PHP脚本调用Redis接口,再次执行命令
lrange number 0 -1
获得结果为1,2。
B用户通过PHP脚本调用Redis接口,也执行相同命令
lrange number 0 -1
获得结果也为1,2。
就这样,A与B用户在内存中(没有通过mysql数据库)实现了数据共享。