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

相关推荐
vivo互联网技术1 天前
从 10 分钟到 1 秒:ES 深度分页任意跳页的三轮优化实战
服务器·数据库·redis·elasticsearch·深度分页
用户3074596982074 天前
Redis 延时队列详解
redis
CSharp精选营4 天前
关系型 vs 非关系型:从原理到选型,一文搞定数据库核心分类
数据结构·nosql·关系型数据库·非关系型数据库·技术选型
烤代码的吐司君4 天前
Redis 数据结构 ZSet, BIT, HyperLogLog,Geo 空间数据
redis·后端
leeyi6 天前
Checkpoint 机制:Agent 怎么在断电后接着跑
redis·aigc·agent
刘马想放假7 天前
Modbus 全栈技术解析:TCP、RTU、ASCII、RTU over TCP
数据结构·网络协议
云技纵横7 天前
一个 @Async 让循环依赖暴雷:Spring 代理的暗坑
redis
犯困蛋挞yy8 天前
用Claude快速解决Redis代码报错反复无解的问题
redis
北域码匠8 天前
冒泡排序太慢?鸡尾酒排序双向优化,原生 C# 零第三方库完整代码
数据结构·排序算法·泛型·c# 算法·鸡尾酒排序·原生 c# 开发·冒泡排序优化·嵌入式算法