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 给了你 阻塞读 命令: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 做消息队列:香在哪,坑在哪?

优点

  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,本次导航结束,下次继续。点关注,不迷路,感谢你的每一次互动~

相关推荐
殷色玫瑰1 小时前
AVL树:从平衡因子到四种旋转的完整实现
c语言·数据结构·c++·算法·visualstudio
y1su2 小时前
Leetcode 二分模板
java·数据结构·算法·leetcode·排序算法
bksczm2 小时前
数据结构的大纲概括
数据结构
千千寰宇3 小时前
[数据库系统] 数据库系统的设计原理研究
数据结构·数据库·database-opengemini/influxdb·database-dament/达梦
殷色玫瑰4 小时前
C++ STL:map 与 set 详解——从底层红黑树到实际应用
c语言·数据结构·c++·算法·visualstudio·rpc
字节跳动的猫4 小时前
LikeShop 商品评价体系二开:追评、晒图审核与评价标签筛选功能开发
运维·数据结构·数据库
范中勤5 小时前
LLM 动态加载与多用户缓存架构技术文档
redis·langchain·llm·缓存架构·多用户隔离
All for pursuit.6 小时前
【动态规划-8】152.乘积最大子数组
数据结构·c++·算法·leetcode·动态规划
信奥卷王6 小时前
[GESP202512 六级] 路径覆盖
数据结构·算法
INGNIGHT6 小时前
1861 · 老鼠跳跃(坐标型dp)
数据结构·c++