不是订阅数本身吃内存,而是卡住的客户端连接把输出缓冲区撑爆了,且这部分内存不计入used_memory;当used_memory_rss暴涨而used_memory变化不大、mem_fragmentation_ratio正常(1.0~1.5)时,应怀疑PUB/SUB输出缓冲区问题。

直接结论:不是订阅数本身吃内存,而是卡住的客户端连接把输出缓冲区撑爆了,且这部分内存不计入 used_memory,监控容易漏掉。
怎么看是不是 PUB/SUB 输出缓冲区在作怪
当 used_memory_rss 暴涨但 used_memory 变化不大、mem_fragmentation_ratio 正常(1.0~1.5),就要怀疑输出缓冲区。立刻执行:
-
redis-cli INFO clients | grep -E "connected_clients|client_longest_output_list"—— 如果client_longest_output_list> 5000,基本坐实 -
redis-cli CLIENT LIST | grep -E "(addr|omem|cmd)"—— 找omem字段超几 MB 的连接,cmd列显示为subscribe或psubscribe -
redis-cli MEMORY DOCTOR—— 它会明确提示 “A large number of clients are subscribed to channels and some of them have a big output buffer”
怎么快速止损并防止再发生
线上已爆内存时,不能等自动断连——必须人工干预或靠配置兜底:
- 立即用
CLIENT KILL TYPE pubsub清掉所有 PUB/SUB 连接(慎用,确认业务可接受短时中断) - 检查
redis.conf是否设置了client-output-buffer-limit pubsub;没设就等于没保险丝 - 推荐配置:
client-output-buffer-limit pubsub 32mb 8mb 60(硬限 32MB,软限 8MB,连续 60 秒超软限则断连) - 硬限建议设为机器物理内存的 5%~10%,避免单个卡死连接打满整机
- 改完必须
CONFIG REWRITE或重启,且要同步到所有集群节点
为什么精简频道名和限制连接数一样重要
每个 SUBSCRIBE channel:xxx 都会让 Redis 在内部 dict 中存一份频道名元数据,不发消息也占内存:
- 频道名越长、数量越多,dict 的哈希桶和字符串对象开销越大;10 万个 64 字节频道名 ≈ 多占 6~8MB
- 避免动态拼接如
user:123456789:order:status:update,改用u123:ord:st:up或哈希截断(如sha256("user:123456789:")[0:8]) - 缩写必须保留语义层级,比如
ord:pay:123和ord:shp:123可分,全压成o:p:123就会混流 - 上线前用
redis-cli --scan --pattern "ord:*" | wc -l估算基数,超 10 万就得考虑按 ID 尾号分片(如ord:0:123)
布隆过滤器真能缓解吗?别被概念带偏
它不解决已建立连接的内存问题,只可能前置拦截无效订阅请求:
- 必须和频道注册流程强绑定:只有真正有生产者调用
PUBLISH前,才BF.ADD频道名 - 客户端订阅前先
BF.EXISTS,返回0就拒绝——但允许极小概率误判(漏掉合法频道) - Redis 原生不支持,得装
redisbloom模块,100 万频道、0.1% 误判率约占 1.2MB 内存 - 别为了省几 MB 引入额外模块和运维负担;优先做好缓冲区限制 + 频道治理
最容易被忽略的一点:PUB/SUB 的内存压力从来不在“订阅动作”,而在“消费是否及时”。只要有一个客户端网络抖动或逻辑阻塞,它的输出缓冲区就会无声无息地膨胀,直到把整台机器拖垮。监控里看不到 used_memory 上涨,不代表没风险。











