题目
Redis List 的原理和使用场景
答案
@[toc] --- List 是简单的字符串列表,**按照插入顺序排序**,可以从头部或尾部向 List 列表添加元素。 列表的最大长度为 `2^32 - 1`,也即每个列表支持超过 `40亿` 个元素。 ## 使用的编码(底层实现) - `REDIS_ENCODING_ZIPLIST`:压缩列表。该编码的使用场景:表对象保存的所有字符串元素的长度都 < 64 字节,并且列表对象保存的元素数量 < 512 个 ![](https://cs-wiki.oss-cn-shanghai.aliyuncs.com/img/20220609171350.png) - `REDIS_ENCODING_LINKEDLIST`:双端链表(每个双端列表的节点都保存了一个字符串对象)。该编码的使用场景:不符合 REDIS_ENCODING_ZIPLIST 使用场景的就用这个 ![](https://cs-wiki.oss-cn-shanghai.aliyuncs.com/img/20220609171403.png) > 注意:**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`)命令实现消息队列 ![](https://cs-wiki.oss-cn-shanghai.aliyuncs.com/list%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97.png) 不过,在消费者调用 `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` 在表尾插入一个,天然的时间有序