一般来说,列表是有序元素的序列:10、20、1、2、3是一个列表。基于数组实现的列表和基于链表( Linked Lists)实现的列表的属性是不同的。
Redis列表是通过链表实现的。这意味着,即使在列表中有数百万个元素,在列表的头部或尾部添加新元素的操作所耗费的时间是恒定的。使用LPUSH命令向包含10个元素的列表头部添加新元素的速度与向包含1000万个元素的列表头部添加一个元素的速度是一样的。
缺点是,通过索引访问一个基于数组实现的Lists中的元素速度是非常快的(恒定的访问时间),而相比之下,在基于链表实现的Lists中速度就没有那么快(在这种情况下,操作需要的时间与被访问元素的索引成正比)。
Redis列表采用链表来实现的主要原因是,对于数据库系统来说,能够以非常快的速度向一个非常长的列表添加元素是非常重要的。另一个强大的优势,就是Redis列表可以在恒定的时间内获取恒定长度的元素。
常用命令:
lpush listname value 向列表的左边(头部)添加一个元素;也可同时添加多个值,用空格隔开
rpush listname value 向列表的右边(尾部)添加一个元素;也可同时添加多个值,用空格隔开
lrange listname 0 -1 获取列表的所有元素
lpop listname 取出列表头部的第一个元素(列表中的元素会减少),如果没有则返回:nil;
rpop listname 取出列表尾部的最后一个元素(列表中的元素会减少),如果没有则返回:nil;
使用场景:
Lists非常有用,下面是两个例子:
1.存储用户在社交网络上发布的最新动态。
2.进程之间的通信。使用消费者-生产者模式,其中生产者将项目推入列表,消费者(通常是worker进程)使用这些项目并执行操作。Redis有特殊的列表命令来使这些操作更加可靠和高效。
例如,流行的Ruby库resque和sidekiq都在底层使用Redis列表来实现。
假设想你在主页显示社交网络中发布的最新照片,希望加快访问速度,可以这样做:
a. 每当用户发布新照片时,我们都会用LPUSH将其ID添加到列表中。
b. 当用户访问主页时,我们使用lrange 0 9来获取最新发布的10个条目。
限定列表(Capped lists)
有时候我们只想使用列表来存储最新的数据,比如,社交网络更新、日志或任何其他东西。
Redis允许我们使用列表作为限定集合,只保持最新的N条记录,使用LTRIM命令丢弃所有旧的的记录。
LTRIM命令与LRANGE类似,但是它不是显示指定范围内的元素,而是将指定范围内的元素设置为列表的值。所有超出给定范围的元素都被删除。
一个非常简单但有用的模式:一起执行列表lpush操作+列表ltrim操作,以便添加一个新元素并丢弃超过限制的元素:
LPUSH mylist <some element>
LTRIM mylist 0 999
阻塞操作
列表的特性使它适合用来实现队列,并且通常作为进程间通信系统的构建块:阻塞操作。
假设你想用一个进程将元素推入列表,并使用不同的进程来实际处理这元素。这就是常见的生产者/消费者模式,可以通过以下简单的方式实现:
1. 将元素推入列表中,生产者会调用LPUSH。
2. 要从列表中提取/处理元素,消费者调用RPOP。
然而,有时候列表是空的,没有什么要处理的,所以RPOP只返回NULL。在这种情况下,消费者必须等待一段时间,然后再用RPOP重试一次。这被称为轮询(polling),在这种情况下使用RPOP并不是一个好主意,因为它有几个缺点:
1. 强制Redis和客户端处理无用的命令(当列表为空时,所有的请求都没有作用,它们只会返回NULL)。
2. 元素的处理会产生延迟,因为在worker接收到NULL之后,它会等待一段时间。即使减小延迟,可以在调用RPOP之间等待更少的时间,但这样会加重问题1,也就是增加对Redis无用的调用。
因此,Redis实现了名为BRPOP和BLPOP的命令,这些命令是RPOP和LPOP的一个版本,当列表为空时,它们会阻塞:只有当列表中添加新元素或达到用户指定的超时时间,它们才会返回给调用者。
brpop tasks 5 #等待列表tasks中的元素,但如果5秒后没有可用元素,则返回
你可以使用0作为超时时间来永远等待列表的元素,并且还可以同时指定多个列表而不仅仅是一个列表,以便同时等待多个列表的,并在第一个列表有可用元素时获得通知。
关于BRPOP有几点需要注意:
1. 客户端以一种有序的方式获得响应:当其他客户端向列表推入一个元素时,阻塞等待这个列表的第一个客户端,优先得到服务。
2. BRPOP返回值与RPOP不同:它返回包含两个元素的数组,(除了值)它还包含键的名称,因为BRPOP和BLPOP能够阻塞等待多个列表中的元素。
3. 如果超时,则返回NULL。
- 可以使用RPOPLPUSH构建更安全的队列或回转队列。
- 还有一个命令的阻塞变体:BRPOPLPUSH。
自动创建和删除键
到目前为止,在我们的示例中,我们从未在推送元素之前创建过空列表,或者当列表内没有元素时删除空列表。Redis的职责是在列表为空时删除键,或者当键不存在,我们试图向其添加元素时,创建一个空列表,例如,LPUSH。
这不是列表特有的,它适用于由多个元素组成的所有Redis数据类型——Sets、Sorted Sets和Hashes。
基本上我们可以用三个规则来概括这个行为:
- 当我们向聚合数据类型(aggregate data type)添加元素时,如果目标键不存在,则在添加元素之前创建一个空的聚合数据类型。
- 当我们从聚合数据类型中删除元素时,如果值为空,则键将自动销毁。
- 对空键调用诸如LLEN(返回列表的长度)之类的只读命令或使用写命令删除元素,总是会产生相同的结果,就好像该键持有命令希望找到的类型的空聚合类型一样。
规则1的例子:
127.0.0.1:6379> exists mylist
(integer) 0
127.0.0.1:6379> lpush mylist 1 2 3
(integer) 3
127.0.0.1:6379> exists mylist
(integer) 1
但如果键已经存在,我们不能对错误的类型进行操作:
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> type foo
string
规则2的例子:
移除所有元素之后,键也不存在了。
127.0.0.1:6379> lpush mylist 1 2 3
(integer) 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
规则3的例子:
127.0.0.1:6379> exists mylist
(integer) 0
127.0.0.1:6379> llen mylist
(integer) 0
127.0.0.1:6379> lpop mylist
(nil)
网友评论