Redis Pub/Sub 不实现推送,仅广播消息给当前在线订阅者;SUBSCRIBE 后客户端阻塞是协议设计,非 bug;其不保证消息可达,断连即丢,需客户端主动兜底,如检查订阅数、结合 List 存储、记录消息 ID 重连拉取等。

Redis 发布订阅模式本身不“实现”推送,它只做一件事:把消息原样、即时、广播式地发给所有当前在线的订阅者。所谓“实时推送”,其实是靠这个机制 + 客户端保持长连接 + 业务层适配共同完成的。
为什么 SUBSCRIBE 后客户端会“卡住”?
这是 Redis Pub/Sub 的设计本质 —— SUBSCRIBE 命令会让客户端进入阻塞状态,不再响应其他命令,直到收到消息或被中断。这不是 bug,是协议要求。
- 一旦执行
SUBSCRIBE channel1,该连接就专用于接收channel1的消息,不能再发GET、SET等请求 - Python 的
pubsub.listen()、Java 的JedisPubSub回调,底层都是轮询get_message()或监听 socket 读事件 - 如果用普通 redis-cli 连接后执行
SUBSCRIBE,终端就“不动了”,必须新开一个窗口发PUBLISH
如何避免消息丢失?
Redis Pub/Sub 不保证消息可达,断连即丢。真正能缓解这个问题的,只有客户端侧的主动策略:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 订阅前先用
pubsub numsub channel1检查频道活跃订阅数,确认服务端至少有一个长期存活的订阅者(比如后台常驻进程) - 关键业务不要依赖单次
PUBLISH,可结合 Redis List(如LPUSH log:events+BRPOP)做兜底存储 - 前端 WebSocket 连接断开时,记录最后已收消息 ID(如果业务有 ID),重连后主动拉取缺失部分 —— Redis 本身不提供 offset 或 ack
- 不要在 Web 请求中直接
SUBSCRIBE,HTTP 生命周期太短,连接一结束订阅就失效
pattern 订阅(PSUBSCRIBE)有什么坑?
PSUBSCRIBE ai:conversations:* 看起来灵活,但实际使用中容易踩三个隐性限制:
- 匹配是字符串前缀匹配,不是 glob 或正则:
ai:conversations:123匹配,但ai:conversations_v2:456不匹配 - 每个 pattern 订阅都会占用一个独立的内部订阅槽位,大量 pattern(如按用户 ID 动态生成)可能导致内存和 CPU 上升
-
pubsub channels查不到 pattern 订阅的频道,只能用pubsub numpat查 pattern 数量,调试困难 - 同一连接不能混用
SUBSCRIBE和PSUBSCRIBE,否则后续命令可能被忽略
Python 示例里 listener 线程为啥要设为 daemon=True?
因为主线程负责输入(input()),而监听线程在后台持续调用 pubsub.listen()。如果不设 daemon=True,程序退出时监听线程不会自动终止,可能卡住进程退出。
- 必须手动调用
pubsub.unsubscribe()和pubsub.close(),否则连接资源不释放,Redis 里会残留 client -
decode_responses=True很关键:否则message['data']是 bytes,split(':')会报TypeError - 消息格式固定为
{'type': 'message', 'pattern': None, 'channel': 'general', 'data': 'user1:hi'},别漏判type != 'message'的心跳或订阅事件
最常被忽略的点:Pub/Sub 不是队列,没有消费者组、没有重试、没有堆积能力。把它当“广播喇叭”用没问题,想当“快递柜”用就会出问题。

















