1. 理论篇
在学习List之前先对其来一点理论的了解是有好处的。
简单来说比如 10,20,1,2,3 的序列就是一个List。不过 List 是继承自ArrayList 还是LinkedList,他们之间的差别还是很大的。LinkedList 意味着即使你的 List 列表中有一百万个数据,在 List 的头或者 尾部插入数据的时间都是恒定的。比如无论数组里面有10个或者1000万个元素,使用LPUSH 把一个元素插入到 List 的头部或者尾部的时候,他们的时间是相等的。
那他的确定是什么呢?在使用下表访问 ArrayList 的元素的时候,它的速度很快,可以说是及时的;而在访问 LinkedList 的元素的额时候,速度就不那么快了,它与访问的元素的下标的数量是有关的。
Redis List 是继承自LinkedList 的,因为对于数据库来说能够在长列表的时候非常快的插入数据是非常关键的。另一个重要的因素是你即将要看到的:Redis 可以在常数时间内取得常数长度。
当访问一个大集合的中部元素是比较重要的时候,还有其他的数据结构可以使用--Sorted Set。我们后续的教程会讲 Sorted Set。
2. Redis List 入门
LPUSH 命令添加一个新元素到一个列表的左边(头部),然而RPUSH 命令添加一个新元素到一个列表的右边(尾部)。最后,LRANGE命令在列表中获取一个范围的元素。
- 127.0.0.1:6666> RPUSH mylist a
- (integer) 1
- 127.0.0.1:6666> rpush mylist b
- (integer) 2
- 127.0.0.1:6666> lpush mylist first
- (integer) 3
- 127.0.0.1:6666> lrange mylist 0 -1
- 1) "first"
- 2) "a"
- 3) "b"
注意LRANGE 有两个下表,范围内的第一个和最后一个元素都会返回。两个下标都可以是负值,这告诉 Redis 从末尾开始计数:所以 -1 是最后一个元素,-2 是倒数第二个元素等等。
正如你所见,RPUSH 添加元素到列表的右边,而LPUSH 添加元素到列表的左边。
这两个命令都是可变参数的命令,这意味着你可以很随意的添加多个元素到列表中。
- 127.0.0.1:6666> rpush mylist 1 2 3 4 5 "mylast"
- (integer) 9
- 127.0.0.1:6666> lrange mylist 0 -1
- 1) "first"
- 2) "a"
- 3) "b"
- 4) "1"
- 5) "2"
- 6) "3"
- 7) "4"
- 8) "5"
- 9) "mylast"
Redis 列表上定义的一个重要的操作是弹出元素(pop elements)。弹出元素操作即使Redis 列表检索元素的操作,同时也是列表消除元素的操作。你可以从左边和右边弹出元素,与你从左边和右边添加元素是非常相似的:
- 127.0.0.1:6666> rpush mylist a b c
- (integer) 3
- 127.0.0.1:6666> rpop mylist
- "c"
- 127.0.0.1:6666> lpop mylist
- "a"
- 127.0.0.1:6666> lpop mylist
- "b"
我们往列表中添加了三个元素,也弹出了三个元素,那么列表现在则是空的。如果我们尝试再继续弹出元素,那么将获得如下结果:
- 127.0.0.1:6666> lpop mylist
- (nil)
如果列表中没有元素,Redis 则会返回一个 NULL 值。
3. 列表的常见使用情形
列表对于多任务是非常又有用的,下面是两个非常有代表性的用法:
- 记录用户提交到社交网络上的最新的更新
- 进程间的通信,使用生产者-消费者模式,生产者把一个条目压进列表中,消费者消费这些条目并且执行动作。Redis 有特别的列表命令可以使这种情况更加可靠和高效。
注:此部分官方文档有实例,可以看看
4.有上限的列表
在很多场景中,我们仅仅使用List存储最新的条目,无论他们是下面的:社交网络更新、日志或者其他什么。
Redis 列表允许我们使用列表作为一个有上限的集合,它使用LTRIM仅存储最新的条目并且丢弃最旧的数据。
LTRIM 命令与LRANGE命令十分相似,它不展示指定的元素范围而是把这个范围设置为新的列表值。给定的范围之外的元素会被移除。
- 127.0.0.1:6666> rpush mylist 1 2 3 4 5
- (integer) 5
- 127.0.0.1:6666> ltrim mylist 0 2
- OK
- 127.0.0.1:6666> lrange mylist 0 -1
- 1) "1"
- 2) "2"
- 3) "3"
下面的 LTRIM 命令告诉Redis仅仅保留从下标0到2的元素,其他的都被丢弃。这就允许一个非常简单的但是有用的模式:对一个列表进行压入操作 +列表调整操作组合,这为了实现添加一个新的元素并且丢弃掉超出限制的元素:
- LPUSH mylist <some element>
- LTRIM mylist 0 999
上面的组合添加一个新的元素,并且仅仅把最新的1000个元素保存入列表中。使用LRANGE你可以访问最顶部的元素而且不需要记住特定的老数据。
注:虽然LRANGE从技术上讲是个O(N) 命令,但是访问列表头部或者尾部的小范围是个及时的操作。
5. 列表的阻塞操作
列表有一个特别的特性使其适合实现队列,并且通常作为一个内部进程通信系统的构建基块:阻塞操作。
比如你想要使用一个进程把条目压入列表,并且使用一个不同的进程来实际上对这些条目做一些工作。这是普通的生产者/消费者模式,并且可以使用下面的方式来实现:
- 要把条目压入列表,生产者调用LPUSH
- 要从列表中提取或者处理条目,消费者调用RPOP
然而,很有可能有时列表是空的并且没有什么可处理的,所以RPOP仅仅返回NULL。在这种情况下,消费者别强制等待一些时间并且使用RPOP重试。这被称作轮询(polling),并且在这种情形下他不是一个好的做法,因为它有如下缺点:
- 强制Redis和客户端运行无用的命令(当列表为空的时候,所有的请求都在做无用功,他们仅仅返回NULL)
- 在消费者NULL的时候,为条目的处理添加一个延迟,它等待一段时间。为了使延迟更小,we could wait less between calls to RPOP, with the effect of amplifying problem number 1, i.e. more useless calls to Redis.
所以,Redis实现了BRPOP 和 BLPOP 命令,他们是当列表为空时可以阻塞版本的 RPOP 和 LPOP :他们仅仅当一个新的元素被添加进入列表的时候或者当用户指定的过期时间到达的时候才返回给调用者。
下面是我们可以在工作者中(worker)调用BRPOP的例子:
- 127.0.0.1:6666> brpop tasks 10
- 1) "tasks"
- 2) "12"
这个效果是我在执行完上述的命令之后,程序阻塞了;然后在另一个客户端执行了 RPUSH tasks 12 命令,这时候阻塞的程序就会继续执行,因为它检测到了有新的元素的进入。
brpop tasks 10 的含义是:等待 tasks 列表中的元素,但是如果在 5 秒以后还没有元素可以获取到的话就返回程序。
注意:你可以使用 0 作为过期时间以一直等待元素,并且你也可以指定多个而不止一个列表,这样可以同时等待多个列表,并且当第一个列表收到元素时收到通知。
关于BRPOP的几个值得注意的地方:
1. 客户端以一种有序的方式接受服务:当一个元素被其他客户端压入到列表中或者其他情况,一个列表阻塞等待的第一个客户端将会接收到服务。
2. 返回值与RPOP相比是不一样的:由于它也包含着键的名字,所以它是两个元素的数组,这是因为BRPOP 和 BLPOP 能够等待来自多个列表的元素。
3. 如果已经到了过期时间,那么将返回NULL。
关于列表和他的阻塞选项,你还有很多需要了解的。关于下述的内容建议你读更多资料:
- 可以使用RPOPLPUSH来构建安全的队列或者循环队列
- BRPOPLPUSH 是命令的命令的阻塞备选方案
额。。。下面依然是我建的一个公众帐号,可以关注一下哦,谢谢