Redis哨兵切换必然导致Pub/Sub断连,因SUBSCRIBE独占TCP连接且无ACK重试机制,旧主下线后连接立即失效;客户端必须新建连接、新建PubSub实例并全量重订阅,不可依赖自动恢复。

Redis哨兵切换时 Pub/Sub 连接断开是必然现象,不是配置错误或客户端 bug。SUBSCRIBE 是长连接,底层 TCP 一断,订阅状态就彻底丢失,服务端不会保留、不回溯、不续订——重连后必须手动重新 SUBSCRIBE。
为什么哨兵切换必然导致 SUBSCRIBE 断连
主从切换瞬间,旧主停止接受新连接,新主尚未完成选举或复制同步;此时客户端仍连着旧主地址(哪怕用了哨兵),TCP 连接会进入半关闭或直接被 RST。而 SUBSCRIBE 不走命令通道,它独占一个 socket,没有 ACK 机制,也没有重试语义。一旦断开,redis-py 的 pubsub.listen() 直接抛 ConnectionError 并退出循环,Jedis 的 JedisPubSub 线程则卡死在 native 阻塞调用里。
- 哨兵本身不接管 Pub/Sub 连接,它只广播拓扑变更(比如往
__sentinel__:hello发消息),不干预已有订阅链路 - 客户端库的 “autoReconnect=true” 只重建 TCP,不恢复频道订阅关系——这是最常被忽略的点
- 即使你用
SENTINEL GET-MASTER-ADDR-BY-NAME动态查新主,DNS 缓存、TIME_WAIT、连接池残留都会让实际重连延迟 1–3 秒,期间所有PUBLISH都静默丢弃
如何实现可靠重连:必须新建连接 + 全量重订
不能依赖任何“自动恢复订阅”的幻觉。每次断连后,必须销毁旧连接、新建 Redis 实例、新建 PubSub 对象、再调用 subscribe()。漏掉任意一步,消息就会漏收。
-
redis-py:别在for msg in pubsub.listen():外层 try/catch;要在整个循环前捕获异常,然后pubsub.close()→r = redis.Redis(...)→pubsub = r.pubsub()→pubsub.subscribe('a', 'b', 'c') - Jedis:必须起独立线程跑
while(true),每次失败后先jedis.close()(防 maxclients 耗尽),再new Jedis(...)和new JedisPubSub(),最后jedis.subscribe(pubsub, "a", "b") - Lettuce:启用
autoReconnect(true)后,必须监听StatefulRedisPubSubConnection.onConnected()回调,在里面显式调用connection.sync().subscribe("a", "b");别用 Spring Bean 缓存 connection 实例
为什么心跳和 keepalive 单独配没用
TCP keepalive(服务端 tcp-keepalive 60)只能探测链路是否通,但 Pub/Sub 连接空闲时不发任何 Redis 协议命令,服务端照样会在 timeout 600 后踢人。而客户端心跳若只对命令连接生效(如 Lettuce 的 pingBeforeActivateConnection),对 PubSub 连接无效。
- 必须对 Pub/Sub 连接单独做应用层心跳:定期发
PING并等PONG,超时即判失联(建议间隔 15–30 秒) - 服务端
config set timeout 600和config set tcp-keepalive 60必须同时生效,否则客户端心跳再勤也扛不住服务端主动断连 - 别忽略 DNS 缓存:JVM 加
-Dsun.net.inetaddr.ttl=60,避免域名解析结果长期指向已下线节点
真正能减少消息丢失的兜底手段
Pub/Sub 本质就是“尽力而为”,断连期间的消息注定无法补。想降低影响,得换思路:
- 用
__sentinel__:hello频道监听哨兵广播,比轮询SENTINEL GET-MASTER-ADDR-BY-NAME更及时、更事件驱动——收到新主 IP 后立刻触发重连流程 - 关键场景改用 Streams:支持消费者组、offset 记录、消息可回溯,天然规避 Pub/Sub 的断连黑洞期
- 退而求其次用 List + Pub/Sub 混合:发布端先
LPUSH queue:events ...再PUBLISH channel:updated 1;订阅端重连后先LRANGE补读,再SUBSCRIBE
最易被忽略的是:所有重连逻辑必须带指数退避(1s → 2s → 4s → 最大 30s),否则网络抖动时大量客户端雪崩重连,反而压垮新主节点。











