Redis 如何实现发布、订阅?
一则或许对你有用的小广告
欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于
Spring AI + Spring Boot3.x + JDK 21..., 点击查看; - 《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot3.x + JDK 17..., 点击查看项目介绍; 演示链接: http://116.62.199.48:7070/; - 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/
Redis 如何实现发布、订阅?
面试考察点
-
基础掌握度:面试官不仅仅是想知道
PUBLISH和SUBSCRIBE这两个命令,更是想知道你是否理解 Redis Pub/Sub 的底层原理(频道字典 + 客户端链表),以及它和消息队列的本质区别。 -
方案对比能力:考察你是否清楚 Redis Pub/Sub 的局限性(消息不持久化、离线客户端丢消息),以及 Redis 5.0 引入的 Stream 类型是如何弥补这些缺陷的。
-
实践应用意识:能否在 Pub/Sub、Stream、专业消息队列(Kafka / RabbitMQ)之间做出合理选型,而不是盲目用 Redis 替代一切。
核心答案
Redis 提供了 3 种 发布订阅实现方式:
| 方式 | 命令 | 特点 | 适用场景 |
|---|---|---|---|
| 频道订阅 | SUBSCRIBE / PUBLISH | 基于频道名精准匹配 | 简单的消息广播 |
| 模式订阅 | PSUBSCRIBE / PUNSUBSCRIBE | 支持通配符匹配(*、?、[ae]) | 按规则批量订阅 |
| Stream(Redis 5.0+) | XADD / XREAD / XGROUP | 持久化、消费者组、消息确认 | 可靠消息队列 |
一句话结论:Redis Pub/Sub 是 轻量级的实时消息广播,适合 "发了就忘" 的场景。如果需要消息持久化、消费确认、积压处理,应该用 Stream 或专业消息队列。
深度解析
一、频道订阅(基础模式)
上图展示了 Redis 频道订阅的核心模型:
- Redis 内部维护了一个频道字典:key 是频道名(如
"news"),value 是订阅了该频道的客户端链表。 - 发布消息时:Redis 查找频道字典,找到所有订阅该频道的客户端,将消息逐一推送给它们。
- 订阅者 C3 没有订阅
"news",所以收不到发布者 A 发到"news"频道的消息。
常用命令:
# 订阅频道(可同时订阅多个)
SUBSCRIBE news sports
# 发布消息
PUBLISH news "breaking: Redis 8.0 released!"
# 退订频道
UNSUBSCRIBE news
# 查看当前活跃频道(至少有一个订阅者)
PUBSUB CHANNELS
# 查看某频道的订阅者数量
PUBSUB NUMSUB news
二、模式订阅(通配符匹配)
上图展示了模式订阅的匹配规则:
*:匹配任意数量的任意字符,包括.分隔符。比如news.*可以匹配news.sports、news.tech.world等。?:匹配单个字符。[ab]:匹配方括号中的任意一个字符。- 一个客户端如果同时订阅了频道
news.sports和模式news.*,会 收到两次 同一条消息。
三、Pub/Sub 的致命局限性
上图列出了 Pub/Sub 的三大局限性:
- 消息不持久化:Pub/Sub 是纯内存操作,消息不会写入 RDB 或 AOF。如果发布消息时没有订阅者,消息直接丢弃。Redis 重启后消息也全部丢失。
- 离线丢消息:订阅者断线期间发布的所有消息都无法找回。
- 缓冲区溢出:如果消息产生速度远大于消费速度,Redis 的客户端输出缓冲区会不断膨胀,最终可能被 Redis 强制断开连接。
四、Stream:Redis 5.0 的消息队列方案
为了弥补 Pub/Sub 的缺陷,Redis 5.0 引入了 Stream 数据类型,提供了接近专业消息队列的能力。
上图展示了 Stream 的消费者组模型,核心要点:
- 消息持久化:Stream 中的消息会持久化到磁盘,Redis 重启后消息不丢失。
- 消费者组:同一个消费者组内,每条消息只会被一个消费者处理,实现负载均衡。
- ACK 机制:消费者处理完消息后发送
XACK,未确认的消息可以被重新投递。 - 历史回溯:可以通过消息 ID 读取任意位置的消息,不限于最新消息。
Stream 常用命令:
# 生产者:发送消息(* 表示自动生成 ID)
XADD mystream * user "张三" action "登录"
# 消费者组:创建消费者组(从最早的消息开始消费)
XGROUP CREATE mystream mygroup 0
# 消费者:读取并处理消息
XREADGROUP GROUP mygroup consumer1 COUNT 1 BLOCK 5000 STREAMS mystream >
# 消费者:确认消息已处理
XACK mystream mygroup 1677891234567-0
# 查看待处理消息(未 ACK 的消息)
XPENDING mystream mygroup
五、三种方案对比与选型
| 对比维度 | Pub/Sub | Stream | 专业 MQ(Kafka) |
|---|---|---|---|
| 消息持久化 | ❌ 不持久化 | ✅ 持久化 | ✅ 持久化 |
| 离线消息 | ❌ 丢失 | ✅ 可回溯 | ✅ 可回溯 |
| 消费者组 | ❌ 无 | ✅ 支持 | ✅ 支持 |
| 消息确认 | ❌ 无 | ✅ ACK 机制 | ✅ ACK 机制 |
| 消息积压 | ❌ 缓冲区溢出 | ✅ 支持 | ✅ 大量积压 |
| 吞吐量 | 极高 | 高 | 极高 |
| 运维复杂度 | 低(Redis 自带) | 低(Redis 自带) | 高(独立集群) |
| 适用场景 | 实时广播、配置通知 | 轻量级消息队列 | 高可靠消息系统 |
选型建议:
- Pub/Sub:实时通知、配置变更广播、集群节点间通信。不需要消息可靠性保证。
- Stream:轻量级任务队列、订单状态变更通知。需要消息不丢失,但不想引入 Kafka。
- Kafka / RabbitMQ:核心业务消息、订单系统、日志采集。需要严格的消息可靠性保障。
面试高频追问
-
追问一:Redis 集群环境下 Pub/Sub 有什么问题?
在 Redis Cluster 中,一条
PUBLISH消息会广播到集群的所有节点,不管该节点上是否有订阅者。这意味着如果集群有 100 个节点,每条消息都会被发送 100 次,造成大量的 网络带宽浪费。所以 Pub/Sub 不适合在大型集群中使用。 -
追问二:Stream 和 Kafka 的区别?Stream 能替代 Kafka 吗?
不能完全替代。Stream 的消息积压能力受限于 Redis 内存,不适合海量消息存储;Kafka 基于磁盘,可以存储 TB 级数据。Stream 适合 小规模、低延迟 的消息场景(单日消息量百万级以内),Kafka 适合 高吞吐、海量积压 的场景。
-
追问三:Spring Boot 中如何使用 Redis Pub/Sub?
Spring Data Redis 提供了
RedisMessageListenerContainer来监听消息,配合RedisTemplate.convertAndSend()发送消息,非常方便。
常见面试变体
- 变体一:"Redis Pub/Sub 和消息队列有什么区别?"
- 变体二:"Redis Stream 了解吗?解决了 Pub/Sub 的什么问题?"
- 变体三:"Redis Pub/Sub 有什么缺点?生产环境怎么选?"
- 变体四:"如何用 Redis 实现一个简单的消息队列?"
记忆口诀
Pub/Sub:广播快、不持久、离线丢 —— "实时喊话,没听到就没了"。
Stream:能持久、能分组、能确认 —— "录音机,随时回放,谁听了有记录"。
选型:简单通知用 Pub/Sub,可靠消息用 Stream,核心业务上 Kafka。
总结
Redis Pub/Sub 是基于频道字典的 实时消息广播机制,优点是简单高效,缺点是消息不持久化、离线丢失。Redis 5.0 引入的 Stream 类型弥补了这些缺陷,支持消息持久化、消费者组和 ACK 确认。选型上,简单通知场景用 Pub/Sub,轻量级可靠消息用 Stream,核心业务推荐 Kafka 等专业消息队列。