Redis PUB/SUB不存储消息,但客户端消费滞后会导致输出缓冲区无限制增长,可能引发OOM;必须显式配置client-output-buffer-limit pubsub硬限、软限和超时时间。

Redis PUB/SUB 本身不存储消息,但客户端堆积会吃光内存
发布订阅(PUB/SUB)在 Redis 中是纯内存转发机制:消息不落盘、不持久化、不存 key。只要所有订阅者在线且消费及时,PUB 一条消息,Redis 就发完即弃,几乎不增加内存压力。
但真实场景中,问题出在「客户端跟不上」——比如某个订阅者网络卡顿、处理逻辑阻塞、或压根没读 SUBSCRIBE 后的响应流。这时 Redis 会为该客户端维持一个输出缓冲区(output buffer),持续缓存待发送消息。这个缓冲区大小不受 maxmemory 约束,会一直涨,直到触发 client-output-buffer-limit 限制或 OOM。
常见现象包括:
-
INFO clients中client_longest_output_list值异常高(比如 >10000) -
CLIENT LIST输出里某连接的omem字段达几十 MB 甚至 GB - 内存监控曲线和某次上线/配置变更时间点强相关,且只影响特定节点
必须配置 client-output-buffer-limit,否则 PUB/SUB 是定时炸弹
Redis 默认对 PUB/SUB 客户端的输出缓冲区**不做硬限制**(pubsub 32mb 8mb 60 是示例值,不是默认)。这意味着一个卡住的订阅者可能把整个实例内存撑爆,而 maxmemory-policy 完全不起作用——因为这些内存不属于数据存储,而是连接上下文的一部分。
正确做法是在 redis.conf 中显式设置:
client-output-buffer-limit pubsub 8mb 4mb 60
含义是:对 PUB/SUB 类型连接,软限制 8MB、硬限制 4MB(单位错误?注意:实际是「软限制=8MB,硬限制=4MB」不成立;正确理解应为「超过 8MB 触发警告,连续 60 秒超过 4MB 则强制断连」——但官方文档写法是 hard-limit soft-limit seconds,所以应为:client-output-buffer-limit pubsub 32mb 8mb 60 表示硬限 32MB、软限 8MB、超时 60 秒)。
实操建议:
- 线上环境禁止使用默认值,必须显式配置
client-output-buffer-limit pubsub - 硬限制(first param)建议设为物理内存的 5%~10%,避免单连接打满整机
- 软限制(second param)设为硬限制的 1/4~1/2,留出反应窗口
- 时间窗口(third param)建议 30~60 秒,太短易误杀,太长延迟止损
- 改完需
CONFIG REWRITE或重启生效,且要同步到所有集群节点
用 MEMORY DOCTOR 和 CLIENT LIST 快速定位 PUB/SUB 内存黑手
当发现某节点内存突增且 INFO memory 显示 used_memory_dataset 并不高,但 used_memory 暴涨,优先怀疑输出缓冲区问题。直接运行:
MEMORY DOCTOR
关注输出中 big_client_buf 是否为 1。如果是,再执行:
CLIENT LIST
过滤出 omem 高的连接:
redis-cli CLIENT LIST | awk -F',' '{for(i=1;i<=NF;i++) if($i ~ /omem=/) print $i}' | sort -t'=' -k2 -n | tail -5
重点看 addr= 和 cmd= 字段——如果 cmd=subscribe 或 cmd=psubscribe 且 omem >5MB,基本就是它了。
临时处置方式:
- 用
CLIENT KILL addr=xxx.xxx.xxx.xxx:yyyy主动断开问题连接 - 检查对应应用日志,确认是否消费逻辑卡死、反序列化失败或未启用自动重连
- 确认客户端是否误用了
MONITOR(它本质也是 PUB/SUB 的极端变体,omem增长更快)
别指望过期策略救 PUB/SUB,它根本没 key 可过期
有人试图给频道名加 EXPIRE 或用 volatile-lru 策略清理,这是无效的。PUB/SUB 不创建任何可被 KEYS 命令扫到的 key,所有状态都在连接上下文中。所谓“频道”只是字符串匹配标识,Redis 不为其分配存储结构。
真正可控的只有两点:
- 控制客户端行为:确保订阅者具备背压能力(如用
READONLY+ 批量READ)、设置合理超时、失败后主动重连而非静默挂起 - 控制服务端水位:靠
client-output-buffer-limit截断失控连接,这是唯一兜底手段
最容易被忽略的是:集群环境下,client-output-buffer-limit 必须在每个分片节点单独配置,不能只改接入层或某几个 master。一个没配的节点,就可能成为整个集群的内存雪崩起点。


















