> 技术文档 > Redis发布订阅:实时消息系统的极简解决方案

Redis发布订阅:实时消息系统的极简解决方案


💡 一句话真相:Redis发布订阅就像\"微信群发通知\"💬——发布者(群主)发送消息,订阅者(群成员)实时接收,无需轮询,毫秒级延迟!


💥 一、为什么需要发布订阅?轮询的致命伤

传统轮询痛点:

Redis发布订阅:实时消息系统的极简解决方案

问题:

  • 95%的请求是无效查询 ⏱
  • 高并发下服务器压力暴增 💥
  • 消息延迟高达数秒 ⏳

发布订阅破局:实时推送,0无效请求!


⚙️ 二、核心概念:三方角色解析

角色 作用 生活化比喻 发布者 发送消息到指定频道 新闻主播 📺 订阅者 监听频道接收消息 电视观众 👀 频道 消息传输的通道 电视频道 📡

#mermaid-svg-0IJdorcnugxJZoU5 {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-0IJdorcnugxJZoU5 .error-icon{fill:#552222;}#mermaid-svg-0IJdorcnugxJZoU5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0IJdorcnugxJZoU5 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-0IJdorcnugxJZoU5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0IJdorcnugxJZoU5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0IJdorcnugxJZoU5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0IJdorcnugxJZoU5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0IJdorcnugxJZoU5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0IJdorcnugxJZoU5 .marker.cross{stroke:#333333;}#mermaid-svg-0IJdorcnugxJZoU5 svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0IJdorcnugxJZoU5 .label{font-family:\"trebuchet ms\",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0IJdorcnugxJZoU5 .cluster-label text{fill:#333;}#mermaid-svg-0IJdorcnugxJZoU5 .cluster-label span{color:#333;}#mermaid-svg-0IJdorcnugxJZoU5 .label text,#mermaid-svg-0IJdorcnugxJZoU5 span{fill:#333;color:#333;}#mermaid-svg-0IJdorcnugxJZoU5 .node rect,#mermaid-svg-0IJdorcnugxJZoU5 .node circle,#mermaid-svg-0IJdorcnugxJZoU5 .node ellipse,#mermaid-svg-0IJdorcnugxJZoU5 .node polygon,#mermaid-svg-0IJdorcnugxJZoU5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0IJdorcnugxJZoU5 .node .label{text-align:center;}#mermaid-svg-0IJdorcnugxJZoU5 .node.clickable{cursor:pointer;}#mermaid-svg-0IJdorcnugxJZoU5 .arrowheadPath{fill:#333333;}#mermaid-svg-0IJdorcnugxJZoU5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0IJdorcnugxJZoU5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0IJdorcnugxJZoU5 .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-0IJdorcnugxJZoU5 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-0IJdorcnugxJZoU5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0IJdorcnugxJZoU5 .cluster text{fill:#333;}#mermaid-svg-0IJdorcnugxJZoU5 .cluster span{color:#333;}#mermaid-svg-0IJdorcnugxJZoU5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0IJdorcnugxJZoU5 :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}发布消息推送消息推送消息发布者频道订阅者1订阅者2


📡 三、工作流程详解

1. 订阅者订阅频道
# 订阅news频道 SUBSCRIBE news 

输出:

1) \"subscribe\" # 订阅成功 2) \"news\" # 频道名 3) (integer) 1 # 当前订阅数 
2. 发布者发送消息
# 向news频道发消息 PUBLISH news \"Redis 7.0 released!\" 

返回值:接收到消息的订阅者数量

3. 订阅者实时接收
# 订阅者终端显示 1) \"message\" # 消息类型 2) \"news\"  # 来源频道 3) \"Redis 7.0 released!\" # 消息内容 

🔧 四、五大进阶玩法

1. 模式订阅(匹配多个频道)
# 订阅所有tech_开头的频道 PSUBSCRIBE tech_* # 发布消息到tech_ai频道 PUBLISH tech_ai \"GPT-4震撼发布!\" 
2. 取消订阅
UNSUBSCRIBE news # 退订指定频道 PUNSUBSCRIBE tech_* # 退订模式频道 
3. 查看活跃频道
PUBSUB CHANNELS # 所有活跃频道 PUBSUB CHANNELS tech* # 匹配tech*的频道 
4. 查看订阅者数量
PUBSUB NUMSUB news # 查看news频道的订阅数 
5. 客户端阻塞监听
import redis r = redis.Redis() pubsub = r.pubsub() pubsub.subscribe(\'news\') # 持续监听消息 for message in pubsub.listen(): if message[\'type\'] == \'message\': print(message[\'data\']) 

🚀 五、四大应用场景实战

1. 实时聊天室

Redis发布订阅:实时消息系统的极简解决方案

2. 系统事件通知
# 订单支付成功通知 PUBLISH order_paid \"订单ID:1001, 金额:299\" 
3. 配置实时更新
# 配置中心发布更新 redis.publish(\'config_update\', \'{\"theme\":\"dark\"}\') # 服务订阅更新 def handle_config(message): new_config = json.loads(message) apply_config(new_config) 
4. 跨服务解耦

#mermaid-svg-VxRCFtAqLJfkAsL9 {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-VxRCFtAqLJfkAsL9 .error-icon{fill:#552222;}#mermaid-svg-VxRCFtAqLJfkAsL9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VxRCFtAqLJfkAsL9 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-VxRCFtAqLJfkAsL9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VxRCFtAqLJfkAsL9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VxRCFtAqLJfkAsL9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VxRCFtAqLJfkAsL9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VxRCFtAqLJfkAsL9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VxRCFtAqLJfkAsL9 .marker.cross{stroke:#333333;}#mermaid-svg-VxRCFtAqLJfkAsL9 svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VxRCFtAqLJfkAsL9 .label{font-family:\"trebuchet ms\",verdana,arial,sans-serif;color:#333;}#mermaid-svg-VxRCFtAqLJfkAsL9 .cluster-label text{fill:#333;}#mermaid-svg-VxRCFtAqLJfkAsL9 .cluster-label span{color:#333;}#mermaid-svg-VxRCFtAqLJfkAsL9 .label text,#mermaid-svg-VxRCFtAqLJfkAsL9 span{fill:#333;color:#333;}#mermaid-svg-VxRCFtAqLJfkAsL9 .node rect,#mermaid-svg-VxRCFtAqLJfkAsL9 .node circle,#mermaid-svg-VxRCFtAqLJfkAsL9 .node ellipse,#mermaid-svg-VxRCFtAqLJfkAsL9 .node polygon,#mermaid-svg-VxRCFtAqLJfkAsL9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VxRCFtAqLJfkAsL9 .node .label{text-align:center;}#mermaid-svg-VxRCFtAqLJfkAsL9 .node.clickable{cursor:pointer;}#mermaid-svg-VxRCFtAqLJfkAsL9 .arrowheadPath{fill:#333333;}#mermaid-svg-VxRCFtAqLJfkAsL9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VxRCFtAqLJfkAsL9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VxRCFtAqLJfkAsL9 .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-VxRCFtAqLJfkAsL9 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-VxRCFtAqLJfkAsL9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VxRCFtAqLJfkAsL9 .cluster text{fill:#333;}#mermaid-svg-VxRCFtAqLJfkAsL9 .cluster span{color:#333;}#mermaid-svg-VxRCFtAqLJfkAsL9 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VxRCFtAqLJfkAsL9 :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}发布事件推送事件推送事件服务ARedis服务B服务C


⚖️ 六、发布订阅 vs 消息队列

特性 发布订阅 消息队列(Stream) 消息持久化 ❌ 消息不保存 ✅ 支持持久化 消费者组 ❌ 不支持 ✅ 支持 消息确认 ❌ 无ACK机制 ✅ 支持ACK 实时性 ⚡ 毫秒级 ⚡ 毫秒级 适用场景 实时通知、广播 任务队列、可靠传输

⚠️ 七、四大生产环境陷阱

🚫 陷阱1:消息丢失无保障

场景:订阅者掉线期间,消息全部丢失!
解决方案:

#mermaid-svg-Z2EWglUo0iLK0FIc {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Z2EWglUo0iLK0FIc .error-icon{fill:#552222;}#mermaid-svg-Z2EWglUo0iLK0FIc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Z2EWglUo0iLK0FIc .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-Z2EWglUo0iLK0FIc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Z2EWglUo0iLK0FIc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Z2EWglUo0iLK0FIc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Z2EWglUo0iLK0FIc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Z2EWglUo0iLK0FIc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Z2EWglUo0iLK0FIc .marker.cross{stroke:#333333;}#mermaid-svg-Z2EWglUo0iLK0FIc svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Z2EWglUo0iLK0FIc .label{font-family:\"trebuchet ms\",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Z2EWglUo0iLK0FIc .cluster-label text{fill:#333;}#mermaid-svg-Z2EWglUo0iLK0FIc .cluster-label span{color:#333;}#mermaid-svg-Z2EWglUo0iLK0FIc .label text,#mermaid-svg-Z2EWglUo0iLK0FIc span{fill:#333;color:#333;}#mermaid-svg-Z2EWglUo0iLK0FIc .node rect,#mermaid-svg-Z2EWglUo0iLK0FIc .node circle,#mermaid-svg-Z2EWglUo0iLK0FIc .node ellipse,#mermaid-svg-Z2EWglUo0iLK0FIc .node polygon,#mermaid-svg-Z2EWglUo0iLK0FIc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Z2EWglUo0iLK0FIc .node .label{text-align:center;}#mermaid-svg-Z2EWglUo0iLK0FIc .node.clickable{cursor:pointer;}#mermaid-svg-Z2EWglUo0iLK0FIc .arrowheadPath{fill:#333333;}#mermaid-svg-Z2EWglUo0iLK0FIc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Z2EWglUo0iLK0FIc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Z2EWglUo0iLK0FIc .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-Z2EWglUo0iLK0FIc .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-Z2EWglUo0iLK0FIc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Z2EWglUo0iLK0FIc .cluster text{fill:#333;}#mermaid-svg-Z2EWglUo0iLK0FIc .cluster span{color:#333;}#mermaid-svg-Z2EWglUo0iLK0FIc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Z2EWglUo0iLK0FIc :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}发布发布者RedisStream备份订阅者

🚫 陷阱2:订阅者阻塞影响服务

错误代码:

while True: message = pubsub.get_message() # 死循环占用CPU 

优化方案:

for message in pubsub.listen(): # 阻塞式监听  process(message) 
🚫 陷阱3:频道爆炸性能下降

案例:创建10万个频道 → Redis内存暴增!
预防措施:

  • 使用模式订阅替代多个频道
  • 清理无效频道:CLIENT KILL TYPE pubsub
🚫 陷阱4:无权限控制

风险:任意客户端可订阅敏感频道(如payment
解决方案:

# 使用代理层鉴权 location /pubsub { auth_request /auth; # 先验证权限  proxy_pass http://redis_backend; } 

📊 八、性能实测:百万级消息压力测试

场景 消息大小 QPS 平均延迟 1万订阅者 100B 85,000 0.3ms 10万订阅者 100B 42,000 0.8ms 1万订阅者 1KB 68,000 0.5ms 10万订阅者 1KB 32,000 1.2ms

💡 测试环境:Redis 7.0,16核CPU/32GB内存,千兆网络


🔧 九、多语言实战代码

Python(redis-py)
import redis # 发布者 publisher = redis.Redis() publisher.publish(\'news\', \'Python 3.12 released!\') # 订阅者 def subscriber(): r = redis.Redis() pubsub = r.pubsub() pubsub.subscribe(\'news\') for message in pubsub.listen(): if message[\'type\'] == \'message\':  print(f\"收到消息: {message[\'data\']}\") # 启动订阅线程 import threading threading.Thread(target=subscriber).start() 
Java(Jedis)
import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPubSub; // 订阅者 JedisPubSub listener = new JedisPubSub() { @Override public void onMessage(String channel, String message) { System.out.println(\"收到: \" + message); } }; new Thread(() -> { Jedis jedis = new Jedis(\"localhost\"); jedis.subscribe(listener, \"news\"); }).start(); // 发布者 Jedis publisher = new Jedis(\"localhost\"); publisher.publish(\"news\", \"Java 21 LTS is out!\"); 
Node.js(ioredis)
const Redis = require(\'ioredis\'); const pub = new Redis(); const sub = new Redis(); // 订阅 sub.subscribe(\'news\', (err) => { if (!err) console.log(\'订阅成功!\'); }); sub.on(\'message\', (channel, message) => { console.log(`收到 ${channel} 消息: ${message}`); }); // 发布 pub.publish(\'news\', \'Node.js 20 released!\'); 

💎 十、总结:发布订阅适用三原则

  1. 实时性要求高:

    • 聊天消息
    • 实时监控报警
  2. 允许消息丢失:

    • 非关键通知(如在线人数更新)
    • 临时状态广播
  3. 无需复杂路由:

    • 广播场景
    • 简单一对一

Redis发布订阅:实时消息系统的极简解决方案

🔥 黄金口诀:

  • 实时广播用PubSub,持久队列用Stream
  • 频道设计要规范,大小消息分得开
  • 生产环境加监控,异常退订及时查

#Redis发布订阅 #实时消息 #分布式系统