List

List 列表是简单的字符串列表,按照插入顺序排序,可以从头部或尾部向列表添加元素。

List最大长度为2的32次方-1,即可以存储超过40亿元素。

> LPUSH myList 1 2 3 4 5 6 7
(integer) 7
> RPUSH myList 0
(integer) 8
> LRANGE myList 0 9
1) "7"
2) "6"
3) "5"
4) "4"
5) "3"
6) "2"
7) "1"
8) "0"

内部实现

List底层由双向链表zipList实现。

遵循以下规则:

  • 如果列表元素小于 512 个(默认值,由list-max-ziplist-entries配置更改),每个元素小于 64 字节(默认值,由 list-max-ziplist-value配置更改),那么会使用 zipList 保存数据。
  • 反之,则使用 双向链表 保存数据。

在 Redis 3.2 版本之后,List 底层数据结构只由 quicklist 实现,替代了双向链表和压缩列表。

应用场景

消息队列

消息队列存取消息三个要求:消息保序、处理重复的消息和保证消息可靠性

Redis 由 ListStream 两种数据类型可以满足以上三个需求。

消息保序

List 本身就可以 LPUSH + RPOP 或者反向 RPUSH + LPOP 来实现消息队列。

以正向来举例:

我们可以将单个 List 抽象为一个消息队列。

生产者使用 LPUSH 命令将数据插入队列头部,如果 key 不存在,则会创建该队列。

消费者使用 RPOP 读取消息,先进先出。

但存在一个问题,在生产者写入数据时,消费者并不能获取通知。如果消费者需要,只能轮询 List 。

为了解决该问题,Redis 提供 BRPOP 命令,BRPOP命令也称为阻塞式读取,客户端在没有读到队列数据时,自动阻塞,直到有新的数据写入队列,再开始读取新数据。和消费者不停地调用 RPOP 命令相比,这种方式能节省CPU开销。

处理重复

为了实现重复消息的判断,主要有两方面要求:

  • 每个消息都有自己唯一的ID。
  • 消费者要能根据ID区分消息是否被处理过。

主要问题在于,List 并不会为每个数据生成 ID,所以必须开发者实现。生成之后,我们在用 LPUSH 命令把消息插入 List 时,需要在消息中包含这个全局唯一 ID。

> LPUSH MessageQueue "10000000001:stock:100"

执行该命令,便已将全局ID为10000000001,库存为100的消息插入消息队列。

消息可靠性

当消费者读取一条消息后,List 中这条消息就被删除。如果消费者在处理消息的过程出现故障或宕机,导致消息没有处理完成,消费者再次启动之后,则无法继续处理未完成的消息。

为了留存消息,Redis 提供了 BRPOPLPUSH 命令,作用是让消费者从一个 List 中读取消息,同时把这个消息再插入到另一个 List(看作备份 List)留存

List作为消息队列的缺陷

List 不支持多个消费者消费同一条消息,因为一旦消费者拉取一条消息后,这条消息就从 List 中删除了,无法被其它消费者再次消费。

要实现一条消息可以被多个消费者消费,那么就要将多个消费者组成一个消费组,使得多个消费者可以消费同一条消息,但是 List 类型并不支持消费组的实现