欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Redis (二) -- List 数据类型

程序员文章站 2022-07-13 16:52:51
...
列表(List)通常有两种实现方案:链表和数组。
Redis 的列表是通过链表方式实现的,其优点是在列表头部或尾部的插入操作时间复杂度是 O(1) ;缺点是通过下标访问元素的效率不及数组列表。
如果需要频繁地访问一个很大集合的中间部分数据,可以采用 Sorted sets 数据结构。
 
小试牛刀
LPUSH 命令从左边(队列头)插入一个元素,RPUSH 命令从右边(队列尾)插入一个元素。LRANGE 命令从队列中取出元素:
127.0.0.1:6379> rpush mylist A
(integer) 1
127.0.0.1:6379> rpush mylist B
(integer) 2
127.0.0.1:6379> lpush mylist first
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "first"
2) "A"
3) "B"
 
LRANGE 命令带两个参数,分别指定获取元素的起始下标和结束下标。下标都可以用负数,比如 -1 指定最后一个元素, -2 指定倒数第二个。
LPUSH 和 RPUSH 命令都可以一次插入多个元素:
127.0.0.1:6379> rpush mylist 1 2 3 4 5 'foo bar'
(integer) 6
127.0.0.1:6379> lrange mylist 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "foo bar"
127.0.0.1:6379> lpush mylist 11 12 13 14 15
(integer) 11
127.0.0.1:6379> lrange mylist 0 -1
 1) "15"
 2) "14"
 3) "13"
 4) "12"
 5) "11"
 6) "1"
 7) "2"
 8) "3"
 9) "4"
10) "5"
11) "foo bar"
 
LPOP 和 RPOP 两个命令可以从队列头(尾)中弹出元素:
127.0.0.1:6379> lrange mylist 0 -1
 1) "15"
 2) "14"
 3) "13"
 4) "12"
 5) "11"
 6) "1"
 7) "2"
 8) "3"
 9) "4"
10) "5"
11) "foo bar"
127.0.0.1:6379> rpop mylist
"foo bar"
127.0.0.1:6379> lrange mylist 0 -1
 1) "15"
 2) "14"
 3) "13"
 4) "12"
 5) "11"
 6) "1"
 7) "2"
 8) "3"
 9) "4"
10) "5"
127.0.0.1:6379> lpop mylist
"15"
127.0.0.1:6379> lrange mylist 0 -1
1) "14"
2) "13"
3) "12"
4) "11"
5) "1"
6) "2"
7) "3"
8) "4"
9) "5"
 
列表常规用法
列表结构用处多多,下面是两个典型场景:
  • 记录用户在社交网络的最近更新。
  • 采用生产者-消费者模式的进程间通信机制。
举例来说,两个流行的 Ruby 库 resque 和 sidekiq  都是基于 Redis 列表实现后台任务的。
Twitter 也是用 Redis 列表管理用户最近发表的微博消息。参见这个文章
 
Capped lists
很多时候我们的需求是保留列表中的最近 N 条记录,这时候可以使用 LTRIM 命令。LTRIM 命令跟 LRANGE 命令相似,不同的是 LTRIM 命令不是把范围内的元素回显,而是丢弃掉范围之外的元素。

 

127.0.0.1:6379> rpush mylist 1 2 3 4 5
(integer) 5
127.0.0.1:6379> ltrim mylist 0 2
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "1"
2) "2"
3) "3"

 

上述命令告诉 Redis 仅保留下标 0 到 2 的元素,其余元素全部丢弃。

 

 

列表上的阻塞式操作
Redis 列表支持阻塞式访问操作,这使得它非常适合做进程间通信机制。
比如说我们的需求是一个进程不断把数据放入到列表中,另外一个进程从列表中读取数据执行任务。这是一个典型的生产者、消费者模式,可以这么实现:
1.生产者调用 LPUSH 命令把数据推到队列中
2.消费者调用 RPOP 命令从队列中读取并处理数据
 
这里其实有一个问题,就是当队列是空的时候,消费者每次调用 RPOP 命令得到的都是一个 NULL,这种情况下消费者不得不等待一段时间再次调用 RPOP 命令探查。这种轮询的方案有两个明显问题:
1.强迫 Redis 和客户端处理大量无用命令。(轮询期间每次 RPOP 返回的都只是 NULL)
2.增加了延迟,轮询一定会有间隔时间,比如间隔时间是一秒钟,那么平均获取到数据的延迟就是 500 毫秒。
不巧的是上述两个问题是此消彼长的关系,属于不可调和矛盾。
因此 Redis 引入了 BRPOP 和 BLPOP 命令,他们是与 RPOP 和 LPOP 对应的阻塞版本。
127.0.0.1:6379> brpop tasks 5
1) "tasks"
2) "do_something"
 
上述命令的含义是:等待 tasks 列表中的元素,如果五秒后还没有,就结束等待,返回 NULL。
超时时间指定为 0 表示一直等待;也可以一次指定监听多个列表:
127.0.0.1:6379> brpop tasks tasks2 0
1) "tasks2"
2) "do_something"
 
关于 BRPOP 命令,下面几点需要注意:
1. 客户端是按序服务的,就是多个客户端同时等待一个列表,如果列表中有值,优先返回给最先进入等待状态的客户端;
2. 返回值跟 RPOP 不一样,是一个两个元素的数组,第一个元素是列表 key 的名字,第二个元素才是列表的值;
3. 超时时间到了,会返回 NULL。
 
自动创建和删除 keys
上面的那些例子中,我们从来没手动创建 key,列表为空的时候也没有手动删除 key,实际上列表 key 的创建和删除操作都是 Redis 内部自动执行的。
不仅列表是这样,Redis 对其他集合类型的数据类型都是这么处理的。
 
关于 Redis key 的自动创建销毁有下面三条基本原则:
1. 向聚合数据类型中添加一个元素,如果目标 key 不存在,先创建一个空的聚合数据类型,再添加元素;
2. 从聚合类型中删除一个元素,如果删除后集合为空,则自动销毁对应的 key;
3. 对空集合调用类似 LLEN 这样的只读命令,或者删除元素命令,Redis 会返回给你一个看起来像这个 key 存在并且握着一个空集合的结果。
 
第一条规则的例子如下:
127.0.0.1:6379> del mylist
(integer) 1
127.0.0.1:6379> lpush mylist 1 2 3
(integer) 3
127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> lpush foo 1 2 3
(error) WRONGTYPE Operation against a key holding the wrong kind of value
 
现在来验证一下第二条规则:
127.0.0.1:6379> lpush mylist 1 2 3
127.0.0.1:6379> exists mylist
(integer) 1
127.0.0.1:6379> lpop mylist
"3"
127.0.0.1:6379> lpop mylist
"2"
127.0.0.1:6379> lpop mylist
"1"
127.0.0.1:6379> exists mylist
(integer) 0
 
接下来验证第三条规则:
127.0.0.1:6379> del mylist
(integer) 0
127.0.0.1:6379> llen mylist
(integer) 0
127.0.0.1:6379> lpop mylist
(nil)
 
翻译自:http://redis.io/topics/data-types-intro