Redis Pub/Sub 不支持消息持久化,是设计使然;需改用 Redis Stream 或应用层兜底实现可靠性,关键在于明确业务需求是广播通知还是可靠事件分发。

Redis Pub/Sub 本身不支持消息持久化,这是设计使然
Redis 的 PUBSUB 模型定位就是轻量、实时、无状态的广播通信——它不存消息,不记消费进度,也不管订阅者是否在线。只要发布者一调用 PUBLISH,消息就发出去;如果此时没人订阅,或者订阅者网络断开,消息直接丢弃,连日志都不会留。这不是 bug,是 feature。
所以别试图给 redis-cli 加个参数、改个配置项,或在 redis.conf 里翻 appendonly 或 save 设置来“开启 Pub/Sub 持久化”——这些只管 Redis 数据库本身的 RDB/AOF,对 Pub/Sub 流量完全无效。
用 Redis Stream 替代 Pub/Sub 是最直接的落地方案
Stream 是 Redis 5.0+ 内置的持久化消息模型,天然支持:消息写入磁盘(AOF/RDB 覆盖)、消费者组(GROUP)、ACK 确认(XACK)、未确认重投(XPENDING)、历史消息回溯(XREAD 从任意 ID 读)。
-
XADD stream:logs * level "ERROR" msg "timeout"—— 发送带结构的消息,ID 自动生成 -
XGROUP CREATE stream:logs mygroup $ MKSTREAM—— 创建消费者组,$表示从最新开始 -
XREADGROUP GROUP mygroup c1 COUNT 1 STREAMS stream:logs >—— 拉取新消息,“>” 表示只读未分配过的 - 处理完必须手动调
XACK stream:logs mygroup {id},否则下次XPENDING还能查到
Spring Data Redis 也已完整支持 Stream:streamOperations.add()、streamOperations.readGroups(),比手写 pubsub 监听器更可控。
硬要保留 Pub/Sub?那就得靠应用层兜底
如果已有大量基于 PUBLISH/SUBSCRIBE 的代码,短期无法重构,就得在业务逻辑里补足缺失的能力:
- 所有关键消息,发布前先
LPUSH msg_queue_backup到一个 List,再PUBLISH;订阅者收到后,异步删掉对应备份(或用LREM安全删除) - 监听器收到消息后,立刻落库(如 MySQL 的
messages表),加字段status ENUM('pending','processed'),定时任务扫pending补发 - 不要依赖
RedisMessageListenerContainer的自动重连——它只重连连接,不重发离线期间的消息;得自己维护一个last_seen_id偏移量,重启后去 Stream 或 DB 拉增量
这种方案不是“修复 Pub/Sub”,而是绕开它,在它外面套一层可靠性壳。代价是代码变重、事务边界变模糊、排查链路变长。
别忽略监控和告警,否则问题永远后知后觉
Pub/Sub 场景下最容易被忽视的是“静默丢失”:没报错、没异常日志,但某类通知就是收不到。这时光看应用日志没用,得盯 Redis 侧指标:
- 用
pubsub numpat和pubsub numsub channel查当前订阅数,突然归零说明客户端批量崩溃或网络隔离 - 在发布端加埋点:记录每次
PUBLISH的返回值(整数,表示收到消息的客户端数),持续为 0 就得告警 - Stream 方案下重点监控
XPENDING返回的 pending 数和 idle 时间,超 5 分钟未 ACK 就触发人工介入
真正的难点不在怎么写代码,而在于想清楚:你到底需要“广播通知”,还是“可靠事件分发”。选错模型,后面所有优化都是在给漏水的桶打补丁。










