登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  Redis

Redis Pub/Sub 订阅者断线后为什么收不到历史消息

来源:17golang原创

时间:2026-09-10 16:45:59 287浏览 收藏

Redis 的 Pub/Sub 订阅者断线后收不到之前错过的历史消息,不是客户端漏写了拉取逻辑,而是这套机制从设计之初就没有历史回放的语义。Redis 官方把 Pub/Sub 定义为 at-most-once(最多一次)投递模式:消息发布的瞬间,只有当下已经连通服务端、并且提前订阅了对应频道的客户端才能收到这条消息。如果客户端中途断线、消息处理失败,哪怕之后立刻重连重新发起订阅,之前错过的消息也不会自动补发。业务如果需要支持消息补偿读取、实现至少一次消费的可靠性,应该把事件写入 Redis Streams 或者独立的业务持久化存储,Pub/Sub 只用来做轻量的实时通知就好。

官方地址:https://redis.io/

要点速览
  • Pub/Sub 是广播通道,不保存消息,也没有订阅者游标;断线期间发布的消息会永久错过。
  • 允许短暂丢失的缓存失效、在线状态和界面刷新可以继续用 Pub/Sub,但重连后要主动读取持久状态。
  • 订单、审计、任务和必须补偿的领域事件,应选择 Redis Streams 或业务数据库,再按消息 ID 恢复。

Redis Pub/Sub 为什么不会补发断线消息

PUBLISH 的工作是把一条消息推给当前匹配频道的订阅者;SUBSCRIBE 建立的是实时监听关系,不是一个可以翻页的消息列表。消息经过广播后不会自动写入某个频道历史,也不存在“订阅者上次读到哪里”的位置。

因此,发布者在 10:00 发送消息,订阅者在 10:01 才重连,即使频道名称、客户端身份和业务内容都一样,10:00 的消息也不会因为重新执行 SUBSCRIBE 而出现。Redis 文档把这种行为称为 at-most-once;断开网络、消费者进程崩溃或回调处理失败时,消息都没有第二次投递机会。

Redis Pub/Sub 广播边界:发布者通过 PUBLISH 消息连接在线订阅者,断线订阅者在边界外,持久状态单独保存
图1:Pub/Sub 只把发布时的消息推给广播边界内的在线订阅者,断线客户端不自动获得历史记录。

先区分实时广播和可恢复数据

排查“重连后少消息”时,先不要急着调大客户端缓冲区。要问的是:这条消息本身是不是业务事实?如果只是“让页面刷新一下”的提示,丢一条通常可以接受;如果它代表支付状态、库存变更或审计记录,就不能把唯一事实放在 Pub/Sub 上。

场景Pub/Sub 是否合适重连后的处理
缓存失效通知适合,允许偶发漏通知重连时按版本或 TTL 主动刷新缓存
在线状态、输入中提示适合,状态很快过期重新上报当前状态,不追历史事件
订单、扣款、审计不应作为唯一通道从数据库或 Streams 按记录/消息 ID 补偿

另外,Pub/Sub 与 Redis 数据库编号没有关系:在一个逻辑数据库上发布,不会让另一个数据库产生独立的频道历史。需要环境隔离时,应把 prod:staging: 这类环境前缀放进频道名。

允许丢失的通知,重连时怎样补当前状态

实时通知可以继续使用 Pub/Sub,但把“可恢复的当前状态”放在普通 Redis key 或业务数据库里。发布者先写入状态,再广播一个轻量通知;订阅者收到通知后读取状态。重连时不追问“我错过了几条消息”,而是直接读取最新状态,避免把广播通道误当成事件日志。

#!/usr/bin/env bash
# 先保存可恢复的当前版本,再发送只负责提醒的实时通知。
redis-cli HSET order:42 status paid version 18
# PUBLISH 返回当前收到消息的订阅者数量,不是历史消息数量。
redis-cli PUBLISH order:changed '{"order_id":42,"version":18}'

# 订阅者重连后主动读取当前状态,而不是等待 Pub/Sub 补发旧消息。
redis-cli HGETALL order:42

这个模式的关键是幂等:通知可以重复、丢失或乱序到达,但持久状态有版本号,消费者读取后能判断是否需要更新。若业务要求知道每个事件,而不是只关心最终状态,就升级为 Streams。

必须恢复的事件改用 Redis Streams

Redis Streams 会保留消息和消息 ID,可以让消费者在重连后从上次位置继续读取,并结合消费组处理确认与待处理消息。它和 Pub/Sub 解决的是两种不同问题:前者偏向可追踪事件流,后者偏向低延迟广播。

Redis Streams 恢复关系:业务事件同时进入持久化记录和 Redis Stream,消息 ID、消费组连接重连消费者,Pub/Sub 负责实时通知
图2:需要恢复的事件应落到持久化记录或 Redis Stream,Pub/Sub 只负责把实时变化快速通知在线消费者。
#!/usr/bin/env bash
# 把订单事件写入 Stream;* 让 Redis 生成可恢复的消息 ID。
redis-cli XADD order-events '*' order_id 42 status paid

# 消费组从未投递位置读取,重连后仍有消息身份可以继续处理。
redis-cli XREADGROUP GROUP billing-worker consumer-a COUNT 10 STREAMS order-events '>'
# 处理成功后确认这条消息,避免把“读到”误当成“业务完成”。
redis-cli XACK order-events billing-worker 1710000000000-0

生产环境还要明确确认时机、重复消费和待处理消息回收策略。不要因为 Pub/Sub 和 Streams 都在 Redis 里,就把两者混用成“先发布、之后一定能补回”的假可靠链路。

常见问题

订阅者重连后重新 SUBSCRIBE,为什么还是没有旧消息?

SUBSCRIBE 只建立新的实时订阅关系,不读取频道历史。旧消息没有被 Pub/Sub 保存,重连只能收到之后发布的消息。

PUBLISH 的返回值是不是消息积压数量?

不是。它表示本次发布时匹配到并收到推送的订阅者数量,不能说明有多少客户端离线,也不能说明消息是否已被业务处理。

缓存失效通知漏掉后一定会造成脏数据吗?

不一定。若缓存有 TTL、版本校验或重连后的主动刷新,漏掉一次通知可以被修复;没有这些补偿机制时,缓存失效事件就不该只依赖 Pub/Sub。

什么时候应该直接换 Redis Streams?

当消息必须保留、需要重放、需要消费确认、需要按消费者进度恢复,或业务能接受至少一次处理并自行幂等时,应优先考虑 Streams。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>