Redis PUB/SUB不支持消息堆积,因无持久化、无缓冲、无背压机制,消息消费慢即丢失;应改用Redis Stream、Kafka或LPUSH/BRPOP等支持可靠传递的方案。

发布端快、订阅端慢,消息堆积怎么办
Redis 的 PUB/SUB 本质是内存级广播,不持久、不排队、不确认。一旦订阅者消费速度跟不上,消息就直接丢——不是“堆积”,而是压根没地方堆。它底层用链表存待投递消息,每个订阅者独享一份副本,但这个链表不支持背压(backpressure),也没有缓冲区控制。
- 别指望
SUBSCRIBE自动限流或等待;它只管收,不反馈水位 - 生产者调用
PUBLISH返回成功 ≠ 消息被任何人收到,更不等于被处理完 - 如果订阅进程卡住或重启,中间所有消息全部丢失,Redis 不做任何补偿
- 单个订阅连接积压超过几万条消息时,可能触发客户端 OOM 或 Redis 内存暴涨(尤其用 Redisson 等封装库时)
为什么不能靠加订阅进程硬扛
加消费者看似能分摊压力,但 Redis PUB/SUB 是「一对多广播」,不是「负载均衡队列」。你起 10 个 SUBSCRIBE 连接,每条消息都会被复制 10 份发出去——带宽、CPU、内存全翻倍,反而加速瓶颈到来。
- Redis 主线程要遍历所有订阅者链表,逐个 copy 消息,O(N) 时间复杂度
- 每个订阅连接都维持独立 socket 和 buffer,连接数上去后,文件描述符和内存开销剧增
- Redisson 默认的重连策略(如
EqualJitterDelay)在频繁断连时可能引发雪崩式重连风暴 - Java 客户端若未显式配置
setConnectionWatcherTimeout(3000),连接假死检测滞后,堆积会持续更久
真正可用的缓解路径
把 PUB/SUB 当作轻量通知通道,而非可靠消息队列。真要解耦速率差,得换角色:让发布端只写,订阅端按需拉取。
- 改用
LPUSH+BRPOP(或BLPOP)组合:消息进 List,消费者阻塞读取,天然支持背压 - 对高可靠场景,直接上 Kafka / RabbitMQ;Redis Stream(
XADD/XREAD)也比PUB/SUB强得多,支持 ACK、消费者组、历史回溯 - 如果必须用
PUB/SUB,发布端要主动降频:比如加RateLimiter控制PUBLISHQPS,或聚合事件再发(如“过去 5 秒内价格变动汇总”) - 监控关键指标:
INFO clients看connected_clients和client_longest_output_list,后者 > 1000 就说明某订阅者严重拖后腿
Redisson 订阅失效的典型诱因
很多人以为是网络问题,其实是连接状态管理失当。Redisson 默认的保活逻辑对瞬时抖动太敏感,又缺乏对消息积压的熔断感知。
- 不要依赖默认重连参数;务必显式设置
config.setReconnectionDelay(new EqualJitterDelay(Duration.ofMillis(200), Duration.ofSeconds(60))) - 禁用自动重订阅(
setAutoReconnect(false)),改为业务层兜底:连接恢复后重新subscribe并同步最新状态 - 避免在订阅回调里做耗时操作(如 DB 写入、HTTP 调用),否则线程池打满,后续消息直接卡住
- 检查日志中是否频繁出现
ConnectionWatchdog: Disconnected from Redis server—— 这是下游已断,但上游还在狂发,必须双向限流
PUB/SUB 天然不适合异步速率差大的场景,它的设计哲学就是「快进快出、丢了不管」。想靠调参或加机器绕过这个限制,只会让问题更隐蔽。真正的分界点在于:你要的是「通知」,还是「交付保证」。

















