Redis 核心数据结构(二)——List 与消息队列

队列、栈、阻塞读------List 能干的活很多,我们得知道它的边界在哪。

本次导航:

  • List 底层长什么样(quicklist 是什么鬼)
  • 队列操作:LPUSH + RPOP,以及反过来的栈
  • 阻塞读:BRPOP 是怎么实现"等一等,别空转"的
  • List 做消息队列的爽点和痛点(以及为啥现在提 Streams)
  • 真实场景:轻量级任务队列、操作日志、最新消息列表

发车前温馨提醒前情提要:

👉Redis 版本演进、新特性与协议那些事儿

👉 5 分钟跑起 Redis(Docker 版)

👉Redis 核心数据结构(一)------ String 与数字

List 比 String 稍微绕一点,但只要记住"左边右边"就能玩转。

一、List 不是链表,是"两头都能操作"的数组

在 Redis 里,List 可以看作一个双向队列:你可以在左边(头)加,右边(尾)加,也可以从左边或右边弹出。这种结构天然适合做队列(先进先出)和栈(后进先出)。

内部实现 :Redis 3.2 之后,List 底层用的是 quicklist------一种"链表 + 压缩列表"的混合体。

简单说就是:切成多个小块,每块内部压缩存储,既节省内存又保持了链表的高效插入删除。你不需要关心它,知道 Redis 帮你优化好了就行。

二、核心命令:左 vs 右

基础操作

命令 功能
LPUSH key value [value...] 从左边插入一个或多个元素
RPUSH key value [value...] 从右边插入一个或多个元素
LPOP key 从左边弹出一个元素
RPOP key 从右边弹出一个元素
LLEN key 获取列表长度
LRANGE key start stop 获取指定范围的元素(索引从 0 开始)
LINDEX key index 获取指定位置的元素

动手试试

如果还没有启动 Redis 容器,可用如下命令启动一个容器先:

docker run -d --name redis-demo -p 6380:6379 redis:8.6.2

进入容器内部并连接:

docker exec -it redis-demo redis-cli

bash 复制代码
# 从右边塞两个任务
RPUSH queue "task1" "task2"

# 从左边再塞一个高优先级的
LPUSH queue "urgent_task"

# 当前队列: ["urgent_task", "task1", "task2"]

# 从右边弹出
RPOP queue   # "task2"
RPOP queue   # "task1"

# 从左边弹出一个
LPOP queue   # "urgent_task"

过程如图所示:

一个命令看全部元素

bash 复制代码
LPUSH letters "a" "b" "c"   # 注意顺序:先 a 进去,然后 b 变成新头,最后 c 变成头
LRANGE letters 0 -1         # 输出 ["c","b","a"],因为 -1 表示到末尾

三、阻塞读:让队列"等消息"不空转

普通 RPOP 如果队列是空的,直接返回 nil。如果你的业务需要一直等着新任务到来,你可能会写一个 while 循环不断 RPOP,但这样会疯狂消耗 CPU。

Redis 给了你 阻塞读 命令:BRPOPBLPOP

bash 复制代码
# 从右边阻塞弹出,超时 30 秒
BRPOP queue 30
# 如果 30 秒内没有新元素,返回 nil
# 如果有,立即返回 [key, value]

典型用法(生产者-消费者)

bash 复制代码
# 消费者:
BRPOP task_queue 0   # 0 表示永远阻塞,直到有任务

当队列为空时,消费者会"挂起",不占 CPU。新任务来了,Redis 通知它继续。

多个队列一起 listen(优先级排队):

bash 复制代码
BRPOP high:queue normal:queue low:queue 30
# 从左到右依次检查哪个队列有数据,取第一个非空队列的元素

四、List 做消息队列:香在哪,坑在哪?

优点

  1. 简单LPUSH + BRPOP 三五行代码就是一个可靠的消息队列。
  2. 阻塞读:不用自己写轮询,省 CPU。
  3. 支持多个消费者 :多个客户端同时 BRPOP,每个消息只会被其中一个消费者取走(天然的负载均衡)。

痛点

问题1:消息可能丢失

RPOP 取出消息后,如果消费者还没来得及处理完就崩了,这条消息就没了。

List 没有"确认机制",不像 Streams 有 XACK

问题2:不支持消息回溯

消息一旦被 POP 出来,就从 List 里删除了。你想重新消费旧消息?没门。

问题3:消费者组弱

Streams 可以给多个消费者分组,同一个组内消息不重复,组与组之间独立。List 做不到。

问题4:生产者太快会撑爆内存

List 没有"流控",消费者慢时就一直积压。Redis 里的数据会越来越多,直到内存爆掉。

总结:List 适合 轻量级、允许偶尔丢消息、追求简单 的场景。

要可靠的消息队列,Redis 5.0+ 请用 Streams(我们后面有专篇)。

五、真实场景:List 能帮你做哪些事

场景1:轻量级任务队列(优先级 + 普通)

python 复制代码
# 生产者
LPUSH high_queue "pay_order_123"
LPUSH normal_queue "send_email_456"

# 消费者(伪代码)
while True:
    # 优先处理 high_queue
    task = BRPOP high_queue normal_queue 0
    process(task)

场景2:操作日志 / 最新消息列表

比如你存最近 10 条用户操作记录:

bash 复制代码
# 每次新操作压到左边
LPUSH user:1001:logs "click_button"
LTRIM user:1001:logs 0 9   # 只保留最新 10 条

LTRIM 的功劳:控制列表长度,避免无限增长。

场景3:临时数据栈(后进先出)

比如用户一次上传多个文件需要逐个处理,处理顺序和上传顺序相反:

bash 复制代码
# 上传时压栈
LPUSH files "a.jpg" "b.jpg" "c.jpg"

# 处理时弹栈
LPOP files   # 先处理 c.jpg
LPOP files   # 再处理 b.jpg

OK,本次导航结束,下次继续。点关注,不迷路,感谢你的每一次互动~

相关推荐
BullSmall1 小时前
Redis AOF 文件损坏报错:完整修复方案
数据库·redis·缓存
shehuiyuelaiyuehao1 小时前
算法12,滑动窗口,将x减到0的最小操作数
java·数据结构·算法
li星野2 小时前
链表通关八题:从反转链表到两数相加,手撕LeetCode高频题
数据结构·学习·leetcode·链表
流年如夢2 小时前
顺序表 -->增、删、查、改等详细操作
c语言·数据结构
wilbertzhou2 小时前
华为4A架构中的信息架构设计方法:从数据资源到战略资产的治理之道
数据结构·togaf·企业架构·4a架构
小年糕是糕手2 小时前
【C/C++刷题集】栈、stack、队列、queue核心精讲
c语言·开发语言·数据结构·数据库·c++·算法·蓝桥杯
小年糕是糕手2 小时前
【C/C++刷题集】顺序表、vector、链表、list核心精讲
c语言·开发语言·数据结构·c++·算法·leetcode·蓝桥杯
流年如夢2 小时前
算法效率:复杂度原理解析
c语言·数据结构·算法
cpp_25014 小时前
P1024 [NOIP 2001 提高组] 一元三次方程求解
数据结构·c++·算法·题解·二分答案·洛谷·csp