redis pub/sub断连后消息永久丢失,重连仅收新消息;必须用list+pub/sub混合模式:先lpush存数据再publish发通知,重连后lrange补读+subscribe监听。

Redis Pub/Sub 本身不支持断连补发——网络闪断后,未消费的消息永久丢失,重连只能收新消息。要实现“自动重连 + 补发”,必须绕开 Pub/Sub 的固有缺陷,用混合模式兜底。
为什么单纯靠 autoReconnect=true 无法补发
Pub/Sub 是纯内存、无状态的长连接,不走复制流、不落盘、不记录 offset。客户端闪断再连上,SUBSCRIBE 只是重新注册监听,服务端不会回推断连期间的任何消息。常见错误认知是:“只要配了重连,就能无缝恢复”,实际只会漏数据。
-
redis-py的retry_on_timeout=True对 Pub/Sub 无效:它只作用于命令执行阶段,而SUBSCRIBE后连接空闲,根本不会触发重试逻辑 - Lettuce 的
autoReconnect=true仅重建连接,不会自动重订频道,更不会查漏补缺 - 所有客户端的“重连成功”日志,都只代表 TCP 连上了,不代表订阅已生效或消息没丢
必须用 List + Pub/Sub 混合模式落地补发
这是目前最轻量、生产验证过的方案:Pub/Sub 只传轻量信号(如 “order_updated”),真实消息体写入 LPUSH 到 List,重连后先读 List 再监听新通知。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 发布端必须原子写:先
r.lpush('list:orders', json_data),再r.publish('chan:orders', '1');顺序反了会导致通知发出但数据没存 - 订阅端启动时,先
lrange list:orders 0 -1拉全量,处理完再ltrim list:orders 0 -1清空(或按业务逻辑lrem删除已处理项) - 为避免重启重复消费,需在处理每条 List 消息前检查幂等 key(如
setnx processed:order_123 1 EX 3600) - 如果 List 数据量大,可改用
lrange list:orders 0 99分批拉取,配合ltrim截断已处理段
心跳 + 重连状态机才是稳定基础
没有健康心跳,客户端可能连着一个“假活”连接(TCP 通但 Redis 卡死),导致既不重连也不报错,静默丢消息。
- 服务端必须设
config set tcp-keepalive 60和config set timeout 600,否则空闲连接 5 分钟就被踢 - Jedis 需在
PubSubThread中起独立定时器,调用该连接实例的jedis.ping(),不能复用连接池里其他连接 - Lettuce 要给
StatefulRedisPubSubConnection监听onException,捕获后手动connection.ping(),再根据响应决定是否触发重连 - 重连必须带退避:从 1s 开始,指数增长(2s、4s、8s…),上限设为 30s,避免雪崩;且每次重连前检查
connection.isClosed(),防止对已关连接调subscribe()报RedisConnectionClosedException
别忽略 DNS 和节点漂移带来的“伪连接”
客户端连的是域名或 VIP,DNS 缓存过期或哨兵切换后,旧连接可能还挂在下线节点上——PING 成功但 PUBLISH 失败,错误是 READONLY You can't write against a read only replica 或无响应。
- JVM 启动加
-Dsun.net.inetaddr.ttl=60强制 DNS 每分钟刷新 - 定期执行
INFO replication或CLUSTER NODES,校验当前连接是否仍指向有效 master - Redisson 用户要开
setDnsMonitoringInterval(3000),并确保setSubscriptionConnectionPoolSize≥ 20,防单连接阻塞
真正能“补发”的不是 Pub/Sub 本身,而是你把消息体存下来、把通知和数据解耦、把连接状态盯紧——三者缺一,闪断后就只剩日志里一行安静的空白。










