Redis Pub/Sub 不是消息队列,它不落盘、不记 offset、不维护消费者状态,消息仅实时推送给当前在线订阅者,断连即丢,不可靠;应改用 Redis Streams 替代,支持持久化、消费者组和手动 ACK。

Pub/Sub 本质就是不存消息
Redis Pub/Sub 不是消息队列,它压根不落盘、不记 offset、不维护消费者状态。发布者调用 PUBLISH 的那一刻,Redis 只做一件事:把消息发给当前所有在线的 SUBSCRIBE 连接,发完即焚。如果没人在线,消息直接丢;如果消费者网络抖动断开 200ms,期间发布的所有消息,一概不补、不查、不回溯。
这不是 Spring Boot 配置问题,也不是代码写错了——是 Redis Pub/Sub 的设计决定的。你用 StringRedisTemplate.convertAndSend() 发得再稳,只要下游没连着,就等于往空房间喊话。
Spring Boot 自动重连也救不了 Pub/Sub
很多人以为加了 Lettuce 的重连配置(比如 lettuce: shutdown-timeout 或 reconnect-delay)就能兜住断连丢消息,其实不能。因为重连成功后,Redis 不会主动推送“断连期间的历史消息”,它只认当前连接状态。订阅者 reconnect 后,得重新 SUBSCRIBE,但之前那几秒的订单创建、支付成功事件,已经永远消失了。
常见错误现象包括:
- 消费者服务滚动重启时,
UNSUBSCRIBE→ 断连 →SUBSCRIBE窗口期丢失消息 - 用
PSUBSCRIBE pattern*监听通配符,某次 TCP 连接异常中断后,重连无法恢复监听上下文 - 多个线程共用一个
MessageListener实例,其中一个处理慢导致 socket 缓冲区溢出,触发READ ERROR后连接静默断开
别在生产环境用 Pub/Sub 做关键事件分发
电商下单后扣库存、发通知、加积分这类链路,只要有一环漏掉,就会引发资损或客诉。而 Pub/Sub 在以下场景必然不可靠:
- 消费者部署为 Kubernetes Pod,每次发布都可能触发重建和短暂失联
- 使用云 Redis 服务(如阿里云、腾讯云),网络抖动更频繁,且无法控制内核参数调优缓冲区
- 业务增长后消费者扩容,新实例上线前无任何机制获取历史事件
这时候硬扛,只会把问题拖到半夜告警。真正该做的,是换数据结构——不是换框架,不是调参数,是把 PUBLISH/SUBSCRIBE 全量替换成 XADD + XGROUP + XREADGROUP。
Streams 才是 Pub/Sub 场景的正确替代方案
Redis Streams 天然适配原来 Pub/Sub 的广播语义,但多了三样东西:持久化存储、消费者组(Consumer Group)、手动 ACK。Spring Boot 里只需改几处:
- 发布端从
stringRedisTemplate.convertAndSend("order:created", json)改成stringRedisTemplate.opsForStream().add(StreamRecords.newRecord().in("order_stream").body(json)) - 订阅端弃用
MessageListener,改用StreamMessageListenerContainer,并显式设置.autoAcknowledge(false) - 业务处理成功后,必须调用
stringRedisTemplate.opsForStream().acknowledge("order_group", "consumer-1", record.getId())
最关键的是:Streams 中每条消息都有 ID,每个消费者组独立维护读取位置(last_delivered_id),哪怕消费者宕机重启,也能从上次 ACK 的位置继续消费——这才是真正能进生产环境的“发布订阅”。
别纠结怎么让 Pub/Sub 不丢消息,它本来就不该承担这个责任。真正容易被忽略的,是团队在架构评审时,仍把 “Redis 有 Pub/Sub” 当作“我们已有消息中间件”的依据。


















