Redis 发布订阅深度剖析:Redis 如何实现发布订阅功能?
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']}")
decode_responses=True让返回直接是字符串,省得手动解码。pubsub.listen()是阻塞迭代器,死循环里等消息。msg['type'] == 'message'过滤掉订阅成功的握手包。- 注意: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
EVAL的2代表后面两个 KEY,Lua 里用KEYS[1]、KEYS[2]占位。- 原子执行:读标志 + 发消息一气呵成,杜绝并发竞态。
- 返回
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 题讲解源文件