一、为什么实现消息队列
消息队列的核心作用是把"生产消息"和"处理消息"分开。
例如订单业务:
用户下单
↓
生产者把订单任务放入 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:可靠地分配、确认、恢复消息。