Redis 消息队列笔记:List 与 Pub/Sub

一、为什么实现消息队列

消息队列的核心作用是把"生产消息"和"处理消息"分开。

例如订单业务:

复制代码
用户下单
    ↓
生产者把订单任务放入 Redis
    ↓
消费者从 Redis 中取出订单任务
    ↓
异步创建数据库订单

这样可以实现:

  • 削峰:高并发请求先进入 Redis,数据库慢慢处理。

  • 异步:用户不必等待全部业务执行完毕。

  • 解耦:生产者只负责发送任务,消费者只负责处理任务。

  • 多实例共享:多个应用可以使用同一个 Redis 队列。

Redis 中常见的消息通信方式包括:

复制代码
List:竞争消费,适合简单任务队列。
Pub/Sub:广播消费,适合实时通知。
Stream:更可靠,适合订单、秒杀等重要业务。

二、Redis List 实现消息队列

1. List 的基本思想

Redis List 是一个有顺序的双端列表,可以从左边或右边放入、取出元素。

最常见的队列组合:

复制代码
生产者:RPUSH
消费者:BLPOP

逻辑如下:

复制代码
左边                                 右边
← BLPOP      [消息1,消息2,消息3]      RPUSH →

生产者从右边加入消息,消费者从左边取消息,因此满足先进先出。

复制代码
先进入的消息,通常先被处理。

这就是 FIFO:

复制代码
First In First Out,先进先出。

2. List 常用命令

命令 作用
LPUSH key value 从左边加入元素
RPUSH key value 从右边加入元素
LPOP key 从左边取出并删除元素
RPOP key 从右边取出并删除元素
BLPOP key timeout 从左边阻塞获取元素
BRPOP key timeout 从右边阻塞获取元素
LRANGE key 0 -1 查看 List 全部内容
DEL key 删除 List

3. 最简单的 List 队列

假设订单队列名称为:

复制代码
order.queue

生产者发送订单消息:

复制代码
RPUSH order.queue "order-1001"
RPUSH order.queue "order-1002"
RPUSH order.queue "order-1003"

此时逻辑结构为:

复制代码
左边                                      右边
[ order-1001 , order-1002 , order-1003 ]

查看全部消息:

复制代码
LRANGE order.queue 0 -1

结果类似:

复制代码
1) "order-1001"
2) "order-1002"
3) "order-1003"

消费者取出一条消息:

复制代码
LPOP order.queue

得到:

复制代码
"order-1001"

此时队列变成:

复制代码
[ order-1002 , order-1003 ]

注意:

复制代码
LPOP 不只是读取消息,而是取出并删除消息。

4. 为什么实际消费通常用 BLPOP

如果队列为空:

复制代码
LPOP order.queue

会返回:

复制代码
(nil)

如果消费者一直循环执行 LPOP

复制代码
LPOP
没有消息
LPOP
没有消息
LPOP
没有消息

这叫轮询,会不断访问 Redis,浪费 CPU 和 Redis 资源。

因此实际消费者通常使用阻塞命令:

复制代码
BLPOP order.queue 0

含义:

复制代码
如果队列有消息,立即取出一条。

如果队列为空,当前客户端阻塞等待。

0 表示永不超时,一直等待。

它类似 Java 中:

复制代码
orderTasks.take();

对应关系:

Java BlockingQueue Redis List
put(message) RPUSH queue message
take() BLPOP queue 0
poll() LPOP queue

5. 多消费者竞争消费

可以开多个终端,分别作为不同消费者。

复制代码
终端 A:生产者
终端 B:消费者 c1
终端 C:消费者 c2

消费者 c1:

复制代码
BLPOP order.queue 0

消费者 c2:

复制代码
BLPOP order.queue 0

生产者发送消息:

复制代码
RPUSH order.queue "order-1001"

Redis 会把这条消息交给某一个正在等待的消费者。

复制代码
c1 或 c2 中只有一个能拿到 order-1001。

另一个消费者不会拿到同一条消息。

这叫:

复制代码
竞争消费。

多个消费者共同竞争队列中的消息,提高处理能力。

注意:

复制代码
BLPOP 一次只取一条消息。

拿到消息后,命令结束。

想继续消费,需要再次执行 BLPOP。

未来在 Java 中会放进循环:

复制代码
while (true) {
    // 阻塞获取消息
    // 处理消息
}

6. List 不存在时是否自动创建

生产者第一次执行:

复制代码
RPUSH order.queue "order-1001"

如果 order.queue 不存在,Redis 会自动创建一个 List,再写入消息。

因此不需要提前创建队列。

不同命令的行为:

操作 队列不存在时的行为
RPUSH / LPUSH 自动创建 List 并写入消息
LPOP / RPOP 返回 (nil),不会创建
BLPOP / BRPOP 阻塞等待,不会创建
取走最后一条消息后 空 List 通常自动删除

例如消费者先执行:

复制代码
BLPOP order.queue 0

即使 order.queue 还不存在,也会阻塞等待。

之后生产者执行:

复制代码
RPUSH order.queue "order-1001"

Redis 会自动创建 List,并立刻将消息交给等待中的消费者。


7. List 的可靠性问题

普通 List 队列的消费过程:

复制代码
队列中有消息
    ↓
消费者 BLPOP / LPOP
    ↓
消息立即从 List 删除
    ↓
消费者开始处理业务

如果消费者取到消息后立刻宕机:

复制代码
消费者拿到 order-1001
    ↓
Redis 已删除 order-1001
    ↓
消费者还没写入 MySQL 就宕机
    ↓
消息丢失

因此:

复制代码
RPUSH + BLPOP 简单,但不能可靠保证消息一定被处理完成。

它适合:

复制代码
日志、简单通知、可容忍少量丢失的异步任务。

不适合:

复制代码
订单创建、支付、库存扣减、秒杀订单等重要业务。

8. List 的改进:处理中队列

可以准备两个 List:

复制代码
order.queue        待处理队列
order.processing   正在处理队列

生产者发送消息:

复制代码
RPUSH order.queue "order-1001"

消费者不直接删除消息,而是使用:

复制代码
BRPOPLPUSH order.queue order.processing 0

含义:

复制代码
从 order.queue 右边取出一条消息,
放到 order.processing 左边,
整个移动过程是原子的。

流程:

复制代码
待处理队列
    ↓
原子移动
    ↓
处理中队列
    ↓
业务处理成功
    ↓
从处理中队列删除消息

处理成功后:

复制代码
LREM order.processing 1 "order-1001"

如果消费者宕机:

复制代码
消息仍保留在 order.processing 中,
不会立即消失。

但这种方案的重试、超时检测、重复消费控制、失败记录等逻辑都要自己实现。

因此 List 可以改进可靠性,但实现复杂度会逐渐接近 Stream。


三、Redis Pub/Sub 发布订阅

1. Pub/Sub 的基本思想

Pub/Sub 是 Publish / Subscribe 的缩写,即发布 / 订阅。

它不是传统"一个消费者取走一条消息"的队列,而是广播机制。

复制代码
发布者 Publisher
    ↓ PUBLISH
频道 Channel
    ↓ SUBSCRIBE
多个订阅者 Subscriber

例如:

复制代码
发布者
    ↓
news
    ├── 订阅者 A 收到
    ├── 订阅者 B 收到
    └── 订阅者 C 收到

一条消息会被所有当前在线、订阅该频道的客户端收到。


2. Pub/Sub 核心命令

命令 作用
PUBLISH channel message 向频道发布消息
SUBSCRIBE channel 精确订阅频道
UNSUBSCRIBE channel 取消精确订阅
PSUBSCRIBE pattern 按模式订阅频道
PUNSUBSCRIBE pattern 取消模式订阅

3. 基础发布与订阅

终端 A 作为订阅者:

复制代码
SUBSCRIBE news

表示订阅频道:

复制代码
news

终端 B 作为发布者:

复制代码
PUBLISH news "hello redis"

返回:

复制代码
(integer) 1

表示当前有 1 个订阅者收到了消息。

终端 A 会收到:

复制代码
1) "message"
2) "news"
3) "hello redis"

含义:

复制代码
message       表示收到普通频道消息
news          消息所属频道
hello redis   消息内容

4. Pub/Sub 的广播特性

假设两个订阅者都执行:

复制代码
SUBSCRIBE news

然后发布者发送:

复制代码
PUBLISH news "秒杀活动开始"

两个订阅者都会收到:

复制代码
秒杀活动开始

这和 List 不同。

List:

复制代码
一条消息通常只会被一个消费者取走。

Pub/Sub:

复制代码
一条消息会广播给所有在线订阅者。

5. Pub/Sub 为什么不适合订单消息

Pub/Sub 不保存历史消息。

假设没有订阅者时发布:

复制代码
PUBLISH news "活动开始"

返回:

复制代码
(integer) 0

表示当前没有在线订阅者。

这条消息不会保存。

之后即使有人执行:

复制代码
SUBSCRIBE news

也收不到之前那条消息。

因此 Pub/Sub 的特点是:

复制代码
先订阅,才能收到之后发布的消息。

如果订阅者断线、宕机、网络异常,断线期间的消息会直接错过。

Pub/Sub 没有:

复制代码
消息持久化
确认机制
Pending List
失败重试
消费者组

所以它不适合:

复制代码
订单
支付
库存扣减
秒杀下单

这些业务必须保证消息可靠处理。


6. Pub/Sub 适合的场景

Pub/Sub 适合"所有在线客户端都应该立刻知道"的通知型场景。

例如:

复制代码
聊天室消息广播
系统维护通知
缓存失效通知
在线用户状态变化
实时监控大屏更新
WebSocket 推送辅助通知

可以把 Pub/Sub 理解为:

复制代码
微信群公告。

在线的人能立刻看到,不在线的人会错过。


7. 订阅多个频道

可以一次订阅多个频道:

复制代码
SUBSCRIBE news system.notice cache.clear

之后只要其中任意频道有消息,当前客户端都能收到。

例如:

复制代码
PUBLISH news "新闻更新"
PUBLISH system.notice "服务器十分钟后维护"
PUBLISH cache.clear "请清理商品缓存"

8. 取消普通订阅

取消某个频道:

复制代码
UNSUBSCRIBE news

取消当前客户端全部普通订阅:

复制代码
UNSUBSCRIBE

对应关系:

复制代码
SUBSCRIBE      订阅精确频道
UNSUBSCRIBE    取消精确频道订阅

9. 模式订阅:PSUBSCRIBE

除了精确订阅频道,还可以按模式订阅。

例如:

复制代码
PSUBSCRIBE news.*

表示订阅所有以 news. 开头的频道。

可以匹配:

复制代码
news.sports
news.tech
news.game
news.local

发布者执行:

复制代码
PUBLISH news.sports "比赛开始"

或者:

复制代码
PUBLISH news.tech "Redis 通知"

模式订阅者都会收到。

模式订阅收到的格式类似:

复制代码
1) "pmessage"
2) "news.*"
3) "news.sports"
4) "比赛开始"

含义:

复制代码
pmessage       表示模式订阅消息
news.*         当前订阅模式
news.sports    实际发布频道
比赛开始        消息内容

10. PUNSUBSCRIBE 的作用

PUNSUBSCRIBE 是取消模式订阅。

例如之前执行:

复制代码
PSUBSCRIBE news.*

现在取消:

复制代码
PUNSUBSCRIBE news.*

取消全部模式订阅:

复制代码
PUNSUBSCRIBE

对应关系:

订阅方式 取消方式
SUBSCRIBE news UNSUBSCRIBE news
PSUBSCRIBE news.* PUNSUBSCRIBE news.*

因此:

复制代码
PUNSUBSCRIBE 的含义是 Pattern Unsubscribe,
即取消模式订阅。

11. 普通订阅和模式订阅重叠的问题

假设同一个客户端既执行:

复制代码
SUBSCRIBE news.sports

又执行:

复制代码
PSUBSCRIBE news.*

之后发布:

复制代码
PUBLISH news.sports "比赛开始"

该客户端可能收到两次消息:

复制代码
一次来自精准订阅 news.sports
一次来自模式订阅 news.*

因此开发时应避免同一个业务对同一频道建立重叠订阅,否则可能重复处理通知。


四、List 与 Pub/Sub 对比

对比项 Redis List Redis Pub/Sub
核心模式 任务队列 消息广播
一条消息由谁处理 一个消费者 所有在线订阅者
消息是否保存 暂时保存在 List 中 不保存
消费者离线时 消息可暂存在队列 直接错过消息
是否支持阻塞等待 支持 BLPOP / BRPOP 订阅后持续接收
是否有确认机制 没有 没有
是否容易丢消息 消费后宕机可能丢 离线或异常时可能丢
典型用途 简单异步任务 实时广播通知
是否适合订单 不够可靠 不适合

五、最终记忆

List 队列

最常见写法:

复制代码
RPUSH order.queue "消息"
BLPOP order.queue 0

含义:

复制代码
生产者从右边放消息,
消费者从左边阻塞取消息,
实现先进先出的竞争消费。

优点:

复制代码
简单
支持阻塞
多个消费者可竞争消费

缺点:

复制代码
消息取出后立即删除
消费者宕机可能导致消息丢失
没有确认、重试、消费者组机制

Pub/Sub

最常见写法:

复制代码
SUBSCRIBE news
PUBLISH news "消息"

含义:

复制代码
发布者向频道广播消息,
所有当前在线订阅者都会收到。

模式订阅:

复制代码
PSUBSCRIBE news.*
PUNSUBSCRIBE news.*

优点:

复制代码
实时广播
使用简单
适合通知类场景

缺点:

复制代码
不保存消息
订阅者离线就会丢消息
没有确认和重试
不适合订单等重要业务

一句话区分

复制代码
List:谁抢到,谁处理。

Pub/Sub:谁订阅,谁都收到。

Stream:可靠地分配、确认、恢复消息。
相关推荐
Devin~Y2 小时前
抖音级短视频推荐与直播带货平台面试实战:从 Java 微服务到 RAG 智能客服全链路解析
java·spring boot·redis·spring cloud·kafka·agent·rag
學點2 小时前
Linux ubuntu安装redis
linux·redis·ubuntu
鹰影472 小时前
一款AI笔记助手和远程同步的markdown笔记idea-note
人工智能·笔记·rust·typescript·react
大明者省3 小时前
四大模态大模型训练体系全解析(架构+范式+分布式+算力成本·)
笔记·分布式·架构
十月的皮皮3 小时前
C语言学习学习笔记20260704-中缀表达式求值(双栈法)
c语言·笔记·学习
智者知已应修善业3 小时前
【 LM358AD方波】2024-12-31
驱动开发·经验分享·笔记·硬件架构·硬件工程
什仙4 小时前
电感规格书全部专业术语完整释义(村田原厂标准定义)
笔记·电感器
ShyanZh4 小时前
【AI笔记】关于大模型原理与提示词工程的碎碎念
人工智能·笔记·机器学习
无小道4 小时前
Redis——哨兵
数据库·redis·缓存·哨兵