Redis PUB/SUB 本身不支持消息确认机制,因其设计为纯内存广播,无ACK、不存消息、不维护消费状态;需改用Redis Stream并配合XGROUP、XREADGROUP和XACK实现可靠投递。

Redis 的 PUB/SUB 本身就不支持确认机制——这不是配置漏了、也不是客户端没写对,而是它压根没设计 ACK 这个环节。
为什么 PUB/SUB 根本不提供消息确认
它不是“不可靠”,而是“不承诺可靠”:发布者调用 PUBLISH 后,Redis 立刻把消息拷贝给所有当前在线的订阅连接,然后马上丢弃。没有存储、不记 offset、不等回执。
- 订阅端网络抖动断开 200ms?断连期间所有
PUBLISH消息永久丢失 - 用
PSUBSCRIBE order.*监听,某次重连没触发PSUBSCRIBE重发?后续所有匹配消息全收不到 - 多个消费者共用一个 SUBSCRIBE 连接,其中一个处理慢导致 socket 缓冲区溢出,
READ ERROR后连接静默断开——没人知道它卡在哪条消息 - 集群模式下,
PUBLISH只在当前节点广播,其他节点的订阅者完全收不到(官方明确不保证跨节点传播)
用 STREAM 替代时必须改写的三个关键点
不能只把 PUBLISH 换成 XADD,否则只是换了个名字继续丢消息。
- 生产者必须用
XADD mystream * event "paid" order_id "123",*表示自动生成单调递增 ID,这是后续定位和重试的基础 - 消费者必须用
XREADGROUP GROUP mygroup consumer1 COUNT 10 STREAMS mystream >启动,>表示只读新消息;首次启动想从头读,得换成0-0 - 每条消息处理完后,**必须显式执行**
XACK mystream mygroup 1718234567890-0,否则该消息会一直留在XPENDING列表里,持续被重复拉取
STREAM 消费者组里最容易踩的坑
很多人以为 consumer1 是随便起的名字,其实它是 Redis 内部强状态标识。
- 如果每次启动都随机生成 consumer 名(比如拼上
os.getpid()或时间戳),等于不断创建新消费者,旧的XPENDING消息永远没人XACK,越积越多 - 同一个消费者组内,不同 consumer 名对应独立的
LAST_DELIVERED_ID和XPENDING列表,无法共享消费进度 - 故障转移时别直接
XCLAIM到新 consumer 名下就完事,要先确认原 consumer 是否真已宕机,否则可能造成重复处理
真正麻烦的不是写法,而是思维切换:从“发完即止”的广播逻辑,转为“每条消息都要追踪生命周期”的日志语义。哪怕只漏掉一次 XACK,或者 consumer 名写错一位,都会让消息卡在 pending 状态不动,而你很难第一时间发现。











