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 由 List
和 Stream
两种数据类型可以满足以上三个需求。
消息保序
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 类型并不支持消费组的实现。