Redis 发布订阅深度剖析:Redis 如何实现发布订阅功能?

Redis 发布订阅深度剖析:Pub/Sub 的底层实现原理

1. Redis 发布订阅

知其然,更要知其所以然!懂原理才能用得爽,今天咱们把Pub/Sub的底裤扒到底。

2. 核心数据结构

  • 频道字典 pubsub_channels
  • 模式链表 pubsub_patterns

就这么简单,字典 + 链表 = 光速广播!

3. 生命周期

一次消息从"出生"到"送达"的全链路

步骤 命令 做了什么
1 SUBSCRIBE chat:room1 把自己塞进 chat:room1 的链表尾部
2 PSUBSCRIBE chat:* 把自己塞进 pubsub_patterns 链表
3 PUBLISH chat:room1 "Hello" Redis 去两个地方找人:① 频道链表全员;② 通配符能匹配的模式链表
4 UNSUBSCRIBE / PUNSUBSCRIBE 把客户端从对应链表摘掉,链表空了顺便删掉频道字典的 Key

4. 优缺点

别被"快"字骗,先想清楚能不能接受"丢"

4.1. 优点

  • 低延迟:消息传递速度极快,通常在 1ms 以内
  • 轻量级:实现简单,资源消耗少
  • 实时性:消息即时推送,无需轮询
  • 多对多通信:支持多个发布者和订阅者

4.2. 缺点

  • 无持久化:消息不存储,离线客户端会丢失消息
  • 无 ACK 机制:无法确认消息是否成功送达
  • 无回溯能力:无法获取历史消息
  • 集群限制:默认情况下只能在单节点内广播

如果你要持久化 / ACK / 回溯,出门右转Redis Stream或Kafka。

5. 代码实战

三分钟跑通"聊天室"

5.1. 场景:聊天室 chat:room1,Python 版最简洁

import redis

# 连接 Redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)

# 1️⃣ 订阅频道
pubsub = r.pubsub()
pubsub.subscribe('chat:room1')

# 2️⃣ 发布消息
r.publish('chat:room1', 'Hello, everyone!')

# 3️⃣ 实时监听
for msg in pubsub.listen():
    if msg['type'] == 'message':
        print(f"[{msg['channel']}] {msg['data']}")
  1. decode_responses=True 让返回直接是字符串,省得手动解码。
  2. pubsub.listen() 是阻塞迭代器,死循环里等消息。
  3. msg['type'] == 'message' 过滤掉订阅成功的握手包。
  4. 注意:Pub/Sub 不存储消息,必须先订阅再发布,否则消息会直接丢失。

5.2. 场景:订单事件广播,Lua 原子脚本版

-- KEYS[1] 标志位, KEYS[2] 频道
if redis.call("GET", KEYS[1]) == "1" then
    return redis.call("PUBLISH", KEYS[2], "Order#123 paid!")
else
    return 0
end

使用方式:

redis-cli EVAL "$(cat order_notify.lua)" 2 flag:pay_ok events:order
  1. EVAL 的 2 代表后面两个 KEY,Lua 里用 KEYS[1]、KEYS[2] 占位。
  2. 原子执行:读标志 + 发消息一气呵成,杜绝并发竞态。
  3. 返回 0 表示条件不满足,调用方可据此重试或记录日志。

6. 集群陷阱

——别让消息困在"单机宇宙"

  • 默认广播只在当前节点,别的实例收不到。
  • 解法 A:所有客户端连同一节点(简单粗暴)。
  • 解法 B:Redis 7.0 的 Sharded Pub/Sub,按 Slot 把频道映射到不同分片。

7. 对比

特性 Pub/Sub Stream Kafka
延迟 < 1 ms ≈ ms 级 ms~百 ms
消息持久化 ❌ ✅ ✅
回溯消费 ❌ ✅ ✅
集群跨节点 ❌(需 Sharded) ✅ ✅
量级 10 万 QPS 万级 百万级

8. 总结

Redis Pub/Sub就像微信群@所有人——够快、够轻,但别指望聊天记录。

真要做可靠队列,记得换工具!

推荐文章:Java 面试 100 题讲解源文件