必须分两层处理:生产端用channel.confirm,消费端关autoack并显式调用ack/nack;autoack=true仅表示网络送达,不保障业务成功,会导致消息未处理即丢失。

Go 中 RabbitMQ 消息确认必须分两层处理:生产端用 channel.Confirm,消费端关掉 autoAck 并显式调用 channel.Ack 或 channel.Nack。缺一不可,否则所谓“确认”只是假象。
为什么不能只靠 autoAck = true?
这是最常见误判点:设成 true 后消费者一收到消息,RabbitMQ 就立刻从队列删掉它——哪怕你的业务逻辑还没跑、panic 了、甚至进程直接退出。消息实际没被处理,却已丢失。
-
autoAck = true仅表示“网络层送达”,不等于“业务层成功” - 管理界面看到的
Unacked数永远为 0,容易误以为一切正常 - 适用于日志采集、埋点等允许丢弃的场景,不适用于订单、支付、库存类业务
channel.Confirm 必须在 basicPublish 前启用
Go 的 streadway/amqp 客户端要求:channel.Confirm 是一次性开关,一旦调用就不可逆;且所有后续 basicPublish 都会进入 confirm 流程。如果漏掉这步,basicPublish 返回 nil 也不报错,但你收不到任何 Ack/Nack 回调。
- 正确顺序:
ch, _ := conn.Channel()→ch.Confirm(false)→ch.Publish(...) -
ch.Confirm(false)的参数是noWait,设false才会等待 broker 确认信道切换完成 - 若在
Publish后才调Confirm,之前发的消息全部无确认保障
消费端手动确认要避开 deliveryTag 复用陷阱
deliveryTag 是 per-channel 单调递增的 64 位整数,不是全局唯一 ID。多个 goroutine 共享一个 channel 实例时,若并发调 Ack,deliveryTag 可能错乱——比如 goroutine A 处理 msg1(tag=1),goroutine B 处理 msg2(tag=2),但 B 先调 Ack(2),A 后调 Ack(1),RabbitMQ 会拒绝后者并报 PRECONDITION_FAILED - unknown delivery tag。
- 每个消费者 goroutine 应独占一个
channel,或用sync.Mutex串行化Ack调用 -
multiple = true时,Ack(5, true)表示 tag ≤ 5 的所有未确认消息都已处理,但前提是这些 tag 确实由当前 channel 发出 - 拒绝消息用
ch.Nack(deliveryTag, false, requeue),requeue = false才真正丢弃,true会重入队列(可能造成无限循环)
异步确认回调里别做阻塞操作
ch.NotifyPublish 返回的 chan amqp.Confirmation 是无缓冲 channel,如果回调函数里执行 HTTP 请求、数据库写入或 sleep,会导致 channel 积压,最终阻塞整个 channel 的 publish 流程——新消息发不出去,老确认又卡住,形成死锁。
- 回调里只做轻量操作:记录日志、更新内存计数器、发信号给 worker goroutine
- 重试逻辑必须另起 goroutine,且需控制并发数,避免雪崩
- 注意
Confirmation结构体的Ack字段是 bool,Multiple字段仅在Ack == true时有意义
真正可靠的确认链条是:生产端 Confirm 确保进队列 + 消费端手动 Ack 确保业务落地。中间任意一环绕过,都等于在关键路径上拆掉一颗螺丝——系统看起来能跑,但抖一下就散架。











