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 如何实现发布、订阅?Redis 如何实现发布、订阅?

面试考察点

  1. 基础掌握度:面试官不仅仅是想知道 PUBLISHSUBSCRIBE 这两个命令,更是想知道你是否理解 Redis Pub/Sub 的底层原理(频道字典 + 客户端链表),以及它和消息队列的本质区别。

  2. 方案对比能力:考察你是否清楚 Redis Pub/Sub 的局限性(消息不持久化、离线客户端丢消息),以及 Redis 5.0 引入的 Stream 类型是如何弥补这些缺陷的。

  3. 实践应用意识:能否在 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.sportsnews.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/SubStream专业 MQ(Kafka)
消息持久化❌ 不持久化✅ 持久化✅ 持久化
离线消息❌ 丢失✅ 可回溯✅ 可回溯
消费者组❌ 无✅ 支持✅ 支持
消息确认❌ 无✅ ACK 机制✅ ACK 机制
消息积压❌ 缓冲区溢出✅ 支持✅ 大量积压
吞吐量极高极高
运维复杂度低(Redis 自带)低(Redis 自带)高(独立集群)
适用场景实时广播、配置通知轻量级消息队列高可靠消息系统

选型建议

  • Pub/Sub:实时通知、配置变更广播、集群节点间通信。不需要消息可靠性保证。
  • Stream:轻量级任务队列、订单状态变更通知。需要消息不丢失,但不想引入 Kafka。
  • Kafka / RabbitMQ:核心业务消息、订单系统、日志采集。需要严格的消息可靠性保障。

面试高频追问

  1. 追问一:Redis 集群环境下 Pub/Sub 有什么问题?

    在 Redis Cluster 中,一条 PUBLISH 消息会广播到集群的所有节点,不管该节点上是否有订阅者。这意味着如果集群有 100 个节点,每条消息都会被发送 100 次,造成大量的 网络带宽浪费。所以 Pub/Sub 不适合在大型集群中使用。

  2. 追问二:Stream 和 Kafka 的区别?Stream 能替代 Kafka 吗?

    不能完全替代。Stream 的消息积压能力受限于 Redis 内存,不适合海量消息存储;Kafka 基于磁盘,可以存储 TB 级数据。Stream 适合 小规模、低延迟 的消息场景(单日消息量百万级以内),Kafka 适合 高吞吐、海量积压 的场景。

  3. 追问三: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 等专业消息队列。