redis pub/sub必然丢消息,因其纯内存广播模型无队列、不记offset、不持久化,只要订阅者断连、消费延迟或网络抖动,消息即刻丢弃且不可恢复。

高并发下 Redis Pub/Sub 为什么必然丢消息
不是“可能”,而是“一定会”。只要并发量上去、消费稍有延迟或网络抖动,PUBLISH 发出去的消息就大概率进黑洞——这不是配置没调好,是 Redis Pub/Sub 的内存广播模型决定的。
它没有队列、不记 offset、不写 AOF/RDB,发布者调用 PUBLISH 后,Redis 就遍历当前在线订阅者的连接缓冲区,挨个 push,push 完立刻丢弃。哪怕只慢 100ms,那期间所有消息都不可恢复。
- 客户端 netty 接收缓冲区默认 16KB(Lettuce)或阻塞式 socket(Jedis),积压满即断连,后续消息全丢
- Redis 默认
client-output-buffer-limit pubsub是 32MB/8MB/60s,超限直接KILL CLIENT,不通知发布者 - 集群模式下,Pub/Sub 不跨 slot 转发,一个频道只在所属哈希槽节点生效,其他节点订阅无效
怎么确认你已经在丢消息了
别等业务出问题才查——PUBLISH 返回成功 ≠ 消息被消费。真正能说明丢消息的信号都在订阅端:
-
redis-cli CLIENT LIST中看到大量pubsub_channels=0或flags=O(表示 output buffer 溢出被踢) - 日志里反复出现
Client closed connection due to client-output-buffer-limit - 消费者重启后,
SUBSCRIBE立刻收到消息,但中间时段的关键事件完全缺失 - 监控
redis_stat_pubsub_channels和redis_stat_pubsub_patterns突降又回升,伴随evicted_clients上升
用 STREAM 替代 Pub/Sub 的最小改动点
不是加个重试或调大 buffer 就能救回来,必须换数据结构。从 PUBLISH/SUBSCRIBE 切到 XADD/XREADGROUP,核心就三步:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 发布端:把
r.publish('order', json.dumps(data))改成r.xadd('order_stream', {'data': json.dumps(data)}) - 消费端:首次运行用
r.xgroup_create('order_stream', 'order_group', '$', mkstream=True),注意mkstream=True必须显式传,否则流不存在会报错 - 拉取消息时用
r.xreadgroup('order_group', 'c1', {'order_stream': '>'}, count=10),处理完必须r.xack('order_stream', 'order_group', msg_id),漏掉XACK就等于没消费
别指望 NOACK 参数省事——它跳过 ACK,但积压消息不会自动清理,反而更难排查漏处理。
STREAM 也踩坑的地方
换用 STREAM 不等于自动可靠,这几个细节一错就白换:
-
XGROUP CREATE不带mkstream=True,流不存在时直接抛异常,不是静默创建 - 消费者重启后,如果没主动
XRANGE order_stream - + COUNT 100扫一遍积压,或者没在XREADGROUP里把>换成具体 ID,断连期间的消息就永远卡在 pending list 里 -
XTRIM order_stream MAXLEN ~1000要配,不然 stream 只增不减;MAXLEN 0不生效,它只在插入时触发裁剪,不保证实时
真正卡住人的从来不是命令怎么写,而是谁负责初始化消费组、谁兜底重试 pending 消息、积压量怎么告警——这些得嵌进业务流程里,不能只靠 Redis 自己。










