Redis PUB/SUB 不支持可靠消费,因输出缓冲区满(默认32MB)会强制断连丢消息;应改用BRPOP+List实现背压或Stream保障持久化、ACK与重放。

Redis PUB/SUB 遇到消费端处理慢,不是“卡死”,而是直接断连丢消息——没有缓冲、没有重试、没有背压控制。它根本就不是为可靠消费设计的。
为什么SUBSCRIBE连接会突然断开且收不到消息
根本原因是 Redis 对每个订阅连接维护一个输出缓冲区(output buffer),一旦消费者读得太慢,缓冲区涨满,Redis 就强制断开连接,所有未读消息永久丢失。这不是网络问题,是 Redis 的自我保护机制在起作用。
-
client-output-buffer-limit pubsub默认值是32mb 8mb 60:硬限 32MB、软限 8MB 持续 60 秒即断连 - 执行
redis-cli client list查看字段omem(当前缓冲区内存)和oll(待发消息条数),若omem接近硬限且oll > 0,就是它了 - 日志里出现
Client X exceeded output buffer limit或客户端报Connection closed by server,基本可确认 - 别指望调大缓冲区就能根治——只是把断连时间往后推,积压仍在,OOM 风险反而升高
用 BRPOP + List 实现真正可控的解耦
如果你需要“拉不动就不给”,也就是天然支持背压、不丢消息、能多消费者共享,LPUSH + BRPOP 是最轻量可靠的替代方案。
- 发布端改用
LPUSH queue_name "msg",消息落地,不依赖客户端在线 - 消费端用
BRPOP queue_name 0阻塞读取,没消息就等,不会撑爆缓冲区 - 多个 worker 可并发
BRPOP同一个 list,Redis 自动负载均衡(但需自行处理重复消费,比如用LREM或幂等逻辑) - 不支持消息回溯?加个
LRANGE queue_name 0 -1快照即可;要历史保留?接上EXPIRE queue_name 3600
必须用广播场景?那就从发布端降频或聚合
硬要留 PUB/SUB,就不能指望消费端变快,得让发布端“别发那么猛”。这不是妥协,是正视 PUB/SUB 的定位:它只适合低延迟、高丢弃容忍、无状态通知。
- 在发布前加
RateLimiter,例如限制PUBLISHQPS ≤ 50/s,避免突发打满缓冲区 - 高频事件(如传感器心跳、价格微调)聚合成批再发,例如 “过去 5 秒共 42 次变动” 替代 42 条独立消息
- 绝对不要在代码里循环创建新
PubSub实例或用redis-cli --subscribe起一堆短命连接——这会加速EMFILE和连接风暴 - 频道命名别滥用动态后缀(如
order:123456),否则PUBSUB NUMSUB无法清理零订阅频道,内存持续泄漏
Stream 才是需要可靠性的正确起点
如果业务要求消息不丢、可重放、有 ACK、支持消费者组,XADD/XREADGROUP 不是“高级选项”,而是唯一合理选择。PUB/SUB 在这些需求面前,连入场券都没有。
-
XADD logs * event "pay_success" id "123"写入,消息持久化,可查可删 -
XREADGROUP GROUP g1 c1 COUNT 1 STREAMS logs >拉取未确认消息,>表示只读新消息 - 处理完必须显式
XACK logs g1 159...,否则消息保留在 pending list 中,可重试 - 消费者组内自动负载均衡,且各 consumer 独立 offset,不怕某一个拖慢全局
最常被忽略的一点:PUB/SUB 的“解耦”是假解耦——它把耦合从代码搬到了连接生命周期和网络吞吐上。真解耦的关键,从来不是换命令,而是承认“通知”和“处理”本就是两个速率、可靠性、失败策略都不同的阶段,该拆就得拆清楚。











