必须设autoAck=false、立即拷贝msg.Body、配QoS限流和心跳保活,三者缺一消息即丢;autoAck=true会导致消息未处理即被Broker删除,msg.Body复用会覆盖,无心跳或QoS则连接断开或消费卡死。

直接用 goroutine 处理消息队列,不是“加个 go 就完事”,而是必须配合手动确认、消息拷贝、QoS 限流和心跳保活——漏掉任意一环,消息就 silently 丢掉。
ch.Consume() 必须设 autoAck=false
RabbitMQ 默认 autoAck=true,意味着只要消息发出去,Broker 就立刻从队列删掉。哪怕你的 handler panic、还没解析 msg.Body、甚至刚进 for range 循环就被 kill,消息也永远消失。
- 显式传
autoAck: false,这是手动确认的强制前提 - 不能依赖默认值;旧库
streadway/amqp和新库amqp091-go都默认true - 确认动作只能由接收该
msg的 goroutine 调用:msg.Ack(false)成功后,msg.Nack(false, true)失败重入队 - 别在
defer里调Ack:panic 会触发 defer,导致误确认
msg.Body 必须立即拷贝再传给 goroutine
msg.Body 是底层复用的 []byte,下一次 for range 迭代就会被覆盖。如果直接把 msg 整个传进新 goroutine,大概率读到空数据或乱码。
- 安全拷贝方式:
data := append([]byte(nil), msg.Body...)或data := make([]byte, len(msg.Body)); copy(data, msg.Body) - 不要传
*msg或msg结构体本身——Ack/Nack方法非线程安全 - 耗时操作(DB 写入、HTTP 请求)必须放新 goroutine,但确认逻辑仍要回到原 goroutine 执行
必须配 QoS + 心跳 + 连接重连
不设 ch.Qos(1, 0, false),RabbitMQ 会持续发消息,未确认堆积后 channel 被限流,消费卡死;不设心跳,60 秒无响应连接就被 Broker 主动断开,出现 read: connection timed out 或 Broken pipe。
立即学习“go语言免费学习笔记(深入)”;
-
ch.Qos(1, 0, false)放在ch.Consume()之前,限制最多 1 条 unacked 消息——这不是性能妥协,是可靠性底线 - 连接字符串加
heartbeat=30参数:"amqp://user:pass@host:5672/%2F?heartbeat=30" - 监听
conn.NotifyClose()和ch.NotifyClose(),收到事件后重建连接和 channel - 别用
conn.IsClosed()判断状态——它只是本地缓存,不可靠
别拿 chan 当消息队列用
chan 是内存管道,程序退出即清空,无持久化、无 ACK、无重试、不跨进程。把它当 RabbitMQ 替代品,等于把数据库写进局部变量还指望重启后还在。
- 常见误用:
http.HandlerFunc里往chan <- msg一塞就返回,消费者 goroutine 崩溃或没启,消息永久丢失 - 微服务部署在不同机器上,
chan根本不通 - 没错误捕获、没日志、没重试的
go func(){}(),失败后零可观测性 - 真正解耦 = 消息落地 + 显式确认 + 可追溯;这三件事
chan一件都做不到
最常踩的坑就三个:autoAck 设成 true、msg.Body 没拷贝就扔进 goroutine、连接没心跳被断开——它们不报错,但消息就是莫名其妙少了一半。


















