列表(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"
列表常规用法
列表结构用处多多,下面是两个典型场景:
- 记录用户在社交网络的最近更新。
- 采用生产者-消费者模式的进程间通信机制。
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