第十八章:Redis的具体使用——list类型(列表)

更新于:2019-02-22 14:09:35

Redis不仅支持string类型的数据,它还支持其他数据结构的数据。就如这节学习的list(列表)数据类型。


一张图理解list(列表)是什么样的:


1.png


list类型其实就是一个双向链表。通过push,pop操作从链表的头部或者尾部添加删除元素。

这使得list既可以用作,也可以用作队列Redis的list类型类似于PHP的数组数据类型


1.png


链表、栈、队列这些数据结构不太熟悉的可以看教程的第八章内容。


list类型操作命令:


1.png


下面开始做演示:


一 key 为goods(商品)


①为goods添加商品数据


lpush goods nokia  htc  heimei  apple  samsung  xiaomi  lenovo  huawei


lpush 命令将一个或多个值插入到列表头部。 当 key 存在但不是列表类型时,返回一个错误。


1.png


②查看goods的商品数据


llen goods //获取列表长度
lrange goods 0 7 //查看最前面的8个数据


llen 命令用于返回列表的长度。 如果列表 key 不存在,则 key 被解释为一个空列表,返回 0 。 如果 key 不是列表类型,返回一个错误。


lrange 返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。


1.png


lindex goods 4


lindex 命令用于通过索引获取列表中的元素。你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。


1.png


③从尾部添加商品数据


rpush goods vivo oppo


rpush 命令将一个或多个值插入到列表尾部。 当 key 存在但不是列表类型时,返回一个错误。


1.png


④删除商品数据


lpop goods
rpop goods


lpop 命令用于移除并返回列表的第一个元素。

rpop 命令用于移除并返回列表的最后一个元素。


1.png


list类型虽然是双向链表,我们主要把它当成或者队列来看,所以删除数据时需要从头或尾一个一个“弹出”。

删除所有的商品数据可以使用上节的(key的)del命令。


⑤裁剪数据


ltrim goods 1 -2


ltrim 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。

下标 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。


1.png


⑥去除重复的数据


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 相等的值。


1.png


⑦修改指定下标的数据


lset goods 3 xiaohei


lset 通过索引来设置元素的值。

当索引参数超出范围,或对一个空列表进行 LSET 时,返回一个错误。


1.png



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数据库)实现了数据共享。