题目
Redis List 的原理和使用场景
答案
@[toc]
---
List 是简单的字符串列表,**按照插入顺序排序**,可以从头部或尾部向 List 列表添加元素。
列表的最大长度为 `2^32 - 1`,也即每个列表支持超过 `40亿` 个元素。
## 使用的编码(底层实现)
- `REDIS_ENCODING_ZIPLIST`:压缩列表。该编码的使用场景:表对象保存的所有字符串元素的长度都 < 64 字节,并且列表对象保存的元素数量 < 512 个

- `REDIS_ENCODING_LINKEDLIST`:双端链表(每个双端列表的节点都保存了一个字符串对象)。该编码的使用场景:不符合 REDIS_ENCODING_ZIPLIST 使用场景的就用这个

> 注意:**Redis 3.2 开始使用 `Quicklist` 替代原有的 `ZipList + List` 的列表对象底层**
## 常用命令
- `LPUSH key value [value ...]`:将一个或多个值value插入到key列表的表头(最左边),最后的值在最前面
- `RPUSH key value [value ...]`:将一个或多个值value插入到key列表的表尾(最右边)
- `LPOP key`:移除并返回key列表的头元素
- `RPOP key`:移除并返回key列表的尾元素
- `LRANGE key start stop`:返回列表key中指定区间内的元素,区间以偏移量start和stop指定,从0开始
- `BLPOP key [key ...] timeout`:从 key 列表表头弹出一个元素,没有就阻塞 timeout 秒,如果 timeout =0 则一直阻塞
- `BRPOP key [key ...] timeout`:从 key 列表表尾弹出一个元素,没有就阻塞 timeout 秒,如果 timeout = 0则一直阻塞
- ......
## 使用场景
### 消息队列
消息队列在存取消息时,必须要满足三个需求,分别是
- 保证消息的有序性(先进先出)
- 处理重复的消息
- 保证消息可靠性
#### 如何保证消息的有序性?
List 可以使用 `LPUSH` + `RPOP` (或者反过来,`RPUSH`+`LPOP`)命令实现消息队列

不过,在消费者调用 `RPOP(LPOP)` 命令读取数据时,有一个潜在的性能风险点。
在生产者往 List 中写入数据时,List 并不会主动地通知消费者有新消息写入,如果消费者想要及时处理消息,就需要在程序中不停地调用 `RPOP(LPOP)` 命令(比如使用一个while(1)循环)。如果有新消息写入, `RPOP(LPOP)` 命令就会返回结果,否则, `RPOP(LPOP)` 命令返回空值,再继续循环。
所以,即使没有新消息写入List,消费者也要不停地调用 pop 命令,带来不必要的性能损失。
为了解决这个问题,Redis提供了 `BRPOP(BLPOP)` 命令,阻塞式地进行读取。客户端在没有读到队列数据时,自动阻塞,直到有新的数据写入队列,再开始读取新数据。和消费者程序自己不停地调用 RPOP 命令相比,这种方式能节省 CPU 开销。
#### 如何处理重复的消息?
简单实现,为每个消息赋值一个全局的 ID。当消费者收到一条消息后,对比收到的消息 ID 和记录的已处理过的消息 ID,来判断当前收到的消息有没有经过处理。如果已经处理过,那么,消费者程序就不再进行处理了。
但是 <u>List 并不会为每个消息生成 ID 号,所以我们需要自行为每个消息生成一个全局唯一ID</u>
举个例子,现在我们要发送一条 key = "star",value = "count:123" 的消息,在用 LPUSH 命令把消息插入 List 之前,我们生成一个全局唯一的 ID:`109812`,和 value 拼接起来:value = "109812:count:123":
```bash
> LPUSH star "109812:count:123"
```
#### 如何保证消息可靠性?
当消费者程序从 List 中读取一条消息后,List 就不会再留存这条消息了。所以,如果消费者程序在处理消息的过程出现了故障或宕机,就会导致消息没有处理完成,那么,消费者程序再次启动后,就没法再次从 List 中读取消息了。
为了留存消息,List 类型提供了 `BRPOPLPUSH` 命令,<u>这个命令的作用是让消费者程序从一个 List 中读取消息,同时,Redis 会把这个消息再插入到另一个 List(可以叫作备份 List)留存</u>。
这样一来,如果消费者程序读了消息但没能正常处理,等它重启后,就可以从备份 List 中重新读取消息并进行处理了。
总结下:
- 保证消息的有序性(先进先出): `LPUSH` + `RPOP` (或者反过来,`RPUSH`+`LPOP`)。阻塞读取:`BRPOP(BLPOP)`
- 处理重复的消息:为每个消息赋值一个全局的 ID
- 保证消息可靠性:`BRPOPLPUSH`
### 关注/消息列表时间轴
这个简单,关注一个就调用 `RPUSH` 在表尾插入一个,天然的时间有序
