必须在反序列化前拦截:禁用decode_responses=False,收到消息后立即检查len(data),超100KB则丢弃并告警,避免内存分配导致卡死。

订阅端收到大消息后卡死,怎么快速跳过
Python 的 redis-py 默认会把 message["data"] 自动解码为字符串,哪怕你只打算丢弃它,UTF-8 解析和内存分配也已发生。卡顿不是因为“处理慢”,而是因为“已经分配了 1MB 对象却还没来得及判断要不要丢”。
必须在反序列化前做长度拦截,且禁用自动解码:
- 初始化连接时加
decode_responses=False:避免无意义的字符串转换开销 - 在
pubsub.listen()循环里,先检查len(message["data"]),再决定是否继续 - 阈值建议设为
102400(100KB),超过就logger.warning("skip oversized msg")+continue - 别用
json.loads()或pickle.loads()包裹整个message["data"]—— 这一步必须放在长度检查之后
Node.js 用 ioredis 订阅时,离线堆积导致 OOM 怎么防
ioredis 默认开启 enableOfflineQueue: true,断网重连期间所有未消费消息会缓存在内存里。一个 500KB 的消息积压 200 条,就是 100MB 内存直接吃掉,GC 都来不及回收。
生产环境必须显式关闭队列缓冲:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 创建客户端时传入
{ enableOfflineQueue: false } - 同时设
retryStrategy控制重连行为,避免频繁断连引发反复堆积 - 配合服务端限流:发布方对 payload 做硬限制,比如
if (Buffer.byteLength(payload) > 102400) throw new Error("too large") - 不要依赖客户端兜底——发布端才是第一道防线
为什么不能靠 Redis 服务端参数调大 buffer 来扛大消息
调大 client-output-buffer-limit pubsub 只是延缓崩溃,不是解决问题。一旦消费者处理速度跟不上,buffer 就变成内存黑洞,最终触发 OOM killer 或强制断连。
真实瓶颈不在网络层,而在消费逻辑本身:
-
client-output-buffer-limit pubsub 33554432 20971520 60(32MB/20MB/60s)这类配置治标不治本 - 真正该做的是:让消费线程不阻塞、不堆积、不等待——用非阻塞轮询 + 显式超时
- Python 示例:
msg = pubsub.get_message(block=False, timeout=0.1),处理单条超 200ms 就丢弃并告警 - Java Lettuce 推荐用
connection.addListener()注册回调,而非BlockingCommand
发布端发了大消息,订阅端已卡住,怎么紧急止损
别等客户端重启。Redis 本身不提供“取消某次 publish”或“清空某频道消息”的能力,但你可以从两个层面快速干预:
- 立刻在服务端执行
PUBSUB NUMSUB your:channel,确认还有多少活跃订阅者;若为 0,说明问题已扩散到其他链路 - 用
CLIENT LIST TYPE pubsub找出卡住的连接 ID,再用CLIENT KILL ID xxx主动断开,逼客户端重建连接 - 临时屏蔽问题频道:在发布方加开关,对
your:channel直接 return,比修代码更快 - 最关键是——别让下一条大消息进来。检查上游是否误把文件二进制、日志 dump、全量快照当消息发了
大消息不是“偶尔出错”,而是系统设计漏洞。发布订阅协议本身没有 payload 校验、没有压缩协商、没有分片机制。真有大内容要广播,要么拆成小块带序号重装,要么换用 Kafka / Pulsar 这类专为消息设计的系统——硬撑只会让故障面越来越广。

















