Redis (二) -- List 数据类型

列表(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
 
 

猜你喜欢

转载自bibithink.iteye.com/blog/2264219