Redis PUB/SUB 遇慢消费者会直接丢消息而非积压;PUBLISH 成功仅表示消息复制到订阅者输出缓冲区,缓冲区满则断连并永久丢失消息。

Redis PUB/SUB 遇到慢消费者会直接丢消息
不是积压、不是排队、不是缓冲——是根本没地方存。PUBLISH 返回成功,只代表 Redis 主线程把消息 copy 到每个订阅者的输出缓冲区;一旦某个 SUBSCRIBE 连接的客户端读取太慢,它的输出缓冲区(output_buffer)涨满,Redis 就强制断连,中间所有未读消息永久丢失。
常见错误现象包括:
-
INFO clients中client_longest_output_list持续 > 1000 - Redis 日志出现
Client X exceeded output buffer limit - 订阅端突然收不到消息,但
PUBLISH仍返回非零值
加订阅进程反而让性能更差
Redis PUB/SUB 是广播模型,不是队列模型。起 5 个 SUBSCRIBE 连接,每条消息就要被复制 5 份、发送 5 次——带宽、内存、CPU 全部线性翻倍,主线程遍历订阅链表的开销也变成 O(N)。
真正的问题不在“不够多”,而在“没法控速”。你无法对单个订阅者限流,也不能让 PUBLISH 等它跟上。
所以别碰 redis-cli --subscribe 多开这种操作,也别在代码里循环 new RedisMessageListenerContainer 实例——这只会加速 OOM 和连接风暴。
必须换方案:用 List + BRPOP 或直接上 Stream
如果只是想保留背压能力,LPUSH + BRPOP 是最轻量的替代:
- 发布端改用
LPUSH queue_name "msg" - 消费端用
BRPOP queue_name 0阻塞读取,天然支持“拉不动就不给” - 多个消费者可共享一个 List,自动负载均衡(注意需配合
LREM或去重逻辑防重复消费)
如果需要历史回溯、ACK、消费者组,直接用 XADD/XREADGROUP:
-
XADD logs * event "pay_success"写入 -
XREADGROUP GROUP g1 c1 COUNT 1 STREAMS logs >拉取未确认消息 - 处理完必须调
XACK,否则消息保留在 pending list 中,可重试
硬要留 PUB/SUB,只能从发布端做妥协
不是“怎么撑住慢消费者”,而是“怎么不让它变慢”。发布端必须主动降频或聚合:
- 用
RateLimiter控制PUBLISHQPS,比如限制为 100/s - 把高频事件聚合成批,例如 “过去 3 秒内共 17 次价格变动” 再发一次
- 关键业务绝不能依赖 PUB/SUB 做最终一致性保障——它连“至少一次”都做不到
最容易被忽略的一点:PUB/SUB 的语义是「通知」,不是「交付」。只要有一条消息没被收到,整个链路就断了。真要解耦速率差,就得接受角色切换——发布端只管写,订阅端按需拉。这个边界划不清,后面所有重连、监控、扩容都是白忙。











