redis pub/sub 下线即丢消息是设计使然,因其无持久化、无偏移量、不维护消费进度;需可靠消息应改用redis stream,支持持久化、消费者组与ack机制。

Redis Pub/Sub 下线就丢消息,这是设计使然,不是 bug
是的,Redis 的 PUB/SUB 模式下,订阅者一旦断连(网络抖动、进程重启、主动 UNSUBSCRIBE),断连期间所有发布的消息**永久丢失**。这不是配置没调好,而是它压根不存消息——没有持久化、没有偏移量、没有重传、不维护消费进度。
常见错误现象:service-A 发布订单创建事件到 order:created 频道,service-B 订阅后因部署重启 3 秒,这 3 秒内产生的 17 条消息全没了,日志里查不到痕迹,监控也无异常。
-
PUB/SUB是纯内存广播模型,只转发给当前在线的client - 新订阅者从第一秒开始收,收不到历史消息——哪怕刚发布完就订阅
- 用
PSUBSCRIBE通配符也没用,同样不回溯 - 别指望
redis-cli连上再SUBSCRIBE就能“捞”回来,不可能
想保留离线消息?必须换 Redis Stream
Redis Stream 是 5.0 引入的真正消息队列结构,支持消息持久化、消费者组(consumer group)、ACK 确认和位点(last_delivered_id)跟踪,这才是替代 RabbitMQ 或 Kafka 的合理选择。
使用场景:需要至少一次投递(at-least-once)、支持断线重连后继续消费、有多个工作节点分摊负载(比如订单履约、日志归集)。
-
XADD写入的消息默认持久化到 AOF/RDB,服务重启不丢 -
XGROUP CREATE创建消费者组后,每个消费者有自己的读取位置 -
XREADGROUP GROUP <group><consumer> STREAMS <stream> ></stream></consumer></group>中的>表示只读新消息;用具体 ID(如0-0)可重放历史 - 消费者处理失败时,不发
XACK,消息会留在PENDING列表,超时后可被其他实例争抢
Pub/Sub 不该用在哪儿?三个典型误用场景
很多团队把 PUB/SUB 当成轻量 MQ 用,结果线上出问题才意识到它根本不扛事。
- 订单状态变更通知:用户下单 → 推送支付、库存、物流服务,任一服务短暂不可用就漏单
- 跨服务数据同步:A 服务更新用户积分,B 服务监听并写入报表,B 重启即丢增量
-
关键告警广播:监控系统发
alert:high_cpu,值班机器人刚好网络波动,告警静默
这些场景的共性是:消息有价值、不能丢、需确认。而 PUB/SUB 的定位其实是 实时状态广播,比如服务发现心跳、WebSocket 在线用户列表刷新、开发环境配置热推——丢了就丢了,下一秒就覆盖。
如果非要用 Pub/Sub,怎么降低风险?
没有银弹,但可以加一层“软保障”,本质是绕开它本身缺陷,靠外部机制兜底。
- 发布端在
PUBLISH前,先用LPUSH把消息存进一个临时list(如pubsub:backup:order:created),设置 TTL 30 秒;订阅端上线后主动LRANGE拉一次最近消息做补偿 - 用
Redis+Webhook组合:发布者同时写Stream和发PUB/SUB,前者保可靠,后者供低延迟 UI 刷新 - 监控层面补漏:用
INFO clients和CLIENT LIST定期检查活跃订阅数,异常下降立即告警,人工介入排查是否批量掉线 - 绝对不要依赖
redis-cli --csv SUBSCRIBE做生产级消费——它没重连逻辑,也没 ACK
真正麻烦的不是技术选型,而是团队对 PUB/SUB 和 Stream 的边界没共识。有人改一行 PUBLISH 为 XADD 就以为迁移完了,却忘了消费者组要手动 XGROUP DESTROY 清理旧状态,否则重启后还在读 2025 年的老消息。











