# 23. Redis 中的发布/订阅(Pub/Sub)是如何实现的?

# 标准答案

Redis 的发布/订阅(Pub/Sub)是一种消息通信机制,允许多个客户端订阅同一个频道(channel),发布者可以向频道发送消息,所有订阅该频道的客户端都会收到消息。其底层实现基于 全局字典结构事件驱动模型,依靠 Reactor 线程 监听频道事件,并使用 非阻塞 IO(epoll/kqueue) 推送消息。

# 答案解析

Redis Pub/Sub 主要由 发布者(Publisher)订阅者(Subscriber)Redis 服务器 组成。当订阅者订阅某个频道后,Redis 服务器会维护一个订阅关系表,当有发布者向该频道发布消息时,Redis 服务器会遍历所有订阅该频道的客户端,并将消息推送给它们。

# 1️⃣ Pub/Sub 的数据结构

Redis 内部使用 字典(dict)+ 链表 维护 频道-订阅者关系

  • pubsub_channels(哈希表):记录 频道(channel) 对应的 订阅者集合
  • pubsub_patterns(链表):记录 模式匹配(pattern) 订阅的客户端。

示例

  • SUBSCRIBE news → Redis 记录 news 频道的订阅者列表
  • PUBLISH news "Hello" → Redis 查找 news 频道的所有订阅者,推送 "Hello" 消息

# 2️⃣ 订阅与发布的过程

#(1) 订阅(SUBSCRIBE)

当客户端执行 SUBSCRIBE channel1,Redis 服务器会执行:

  1. 将该客户端 ID 添加到 pubsub_channels[channel1] 订阅者列表
  2. 监听该频道的消息,当 channel1 有新消息时,立即推送给该客户端。
127.0.0.1:6379> SUBSCRIBE news
Reading messages... (press Ctrl-C to quit)
1
2

底层实现

  • Redis 维护一个 pubsub_channels 字典,key=频道value=订阅该频道的客户端列表
  • 订阅者被存储在 dict<channel, list<client>> 结构中。

#(2) 发布(PUBLISH)

当某个客户端 PUBLISH news "Breaking News!",Redis 会执行:

  1. 查找 pubsub_channels,获取所有订阅 news 的客户端
  2. 使用 Redis 的事件驱动模型(Reactor)非阻塞推送消息
  3. 如果订阅者很多,Redis 会遍历所有订阅者,逐个推送消息
127.0.0.1:6379> PUBLISH news "Breaking News!"
(integer) 3  # 表示有 3 个客户端接收到了消息
1
2

底层实现

  • Redis 遍历 pubsub_channels["news"] 的所有订阅者,并向它们发送 "Breaking News!"
  • 由于 Redis 单线程处理,它使用 高效的事件循环(epoll/kqueue) 进行消息分发。

# 3️⃣ Redis 的消息推送机制

Redis 采用 基于事件驱动的 IO 多路复用(epoll/kqueue),当 PUBLISH 触发时,Redis 会:

  1. 遍历所有订阅该频道的客户端
  2. 将消息加入到客户端的输出缓冲区(output buffer)
  3. 通过 Redis 的 write 事件发送数据给订阅者(非阻塞 IO)。

🚀 高效推送机制

  • 单线程模型:避免锁竞争,所有操作 O(1) 或 O(N)(N 为订阅者数)。
  • 事件驱动:采用 IO 多路复用,消息发布后,Redis 立即推送给订阅者,避免轮询。

# 4️⃣ 模式匹配订阅(PSubscribe)

Redis 支持模式匹配订阅,允许订阅者监听 多个匹配的频道

127.0.0.1:6379> PSUBSCRIBE news*
1

底层原理

  • Redis 维护 pubsub_patterns,其中存储 dict<pattern, list<client>>
  • PUBLISH "news_sports" "Score: 3-1" 时,Redis 会匹配所有符合 news* 模式的订阅者,并发送消息。

# 5️⃣ Pub/Sub 的缺陷

  1. 消息无持久化:如果订阅者掉线,离线消息不会存储,重连后无法获取之前的消息。
  2. 消息无确认机制:Redis 采用**“尽力而为”**的方式推送消息,无法保证所有订阅者都收到。
  3. 订阅端阻塞:当客户端 SUBSCRIBE 某个频道后,该连接进入阻塞模式,只能接收消息,无法执行其他命令。

# 深入追问

🔹 Redis Pub/Sub 和 Kafka、RabbitMQ 有什么区别?
🔹 如何在 Redis Pub/Sub 上实现持久化消息?
🔹 Redis 如何优化 Pub/Sub 的推送性能?

# 相关面试题

🔹 Redis 的 Pub/Sub 适用于什么场景?
🔹 Redis 如何处理大量订阅者的消息推送?
🔹 如何基于 Redis 实现可靠的消息队列?

# 总结

Redis Pub/Sub 是基于 事件驱动+字典索引 结构实现的消息发布/订阅模型,具有高吞吐、低延迟的特点,但缺少持久化确认机制,适用于实时消息推送,但不适用于可靠消息队列。🚀