队列、栈、阻塞读------List 能干的活很多,我们得知道它的边界在哪。
本次导航:
- List 底层长什么样(quicklist 是什么鬼)
- 队列操作:
LPUSH+RPOP,以及反过来的栈 - 阻塞读:
BRPOP是怎么实现"等一等,别空转"的 - List 做消息队列的爽点和痛点(以及为啥现在提 Streams)
- 真实场景:轻量级任务队列、操作日志、最新消息列表
发车前温馨提醒前情提要:
👉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 给了你 阻塞读 命令:BRPOP 和 BLPOP。
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 做消息队列:香在哪,坑在哪?
优点
- 简单 :
LPUSH+BRPOP三五行代码就是一个可靠的消息队列。 - 阻塞读:不用自己写轮询,省 CPU。
- 支持多个消费者 :多个客户端同时
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,本次导航结束,下次继续。点关注,不迷路,感谢你的每一次互动~