必须设autoack=false、立即拷贝msg.body、qos限流并配心跳、kafka禁用readmessage改用fetchmessage+手动commit——三者缺一消息即丢。

autoAck设为false不是可选项,是保命线
RabbitMQ 的 ch.Consume() 默认 autoAck=true,意味着消息一发出去就被服务端标记为“已确认”,哪怕你的业务代码还没开始解析 msg.Body,甚至刚进 goroutine 就 panic,这条消息也永远消失了。这不是丢数据,是直接跳过可靠性校验。
- 必须显式传入
autoAck: false,不能依赖默认值 - 设为
false后,msg.Ack(false)和msg.Nack(false, true)才有意义 -
msg.Reject(true)可丢弃消息(慎用),msg.Nack(false, true)才能重入队 - 别在
defer里调Ack:panic 会触发 defer,导致误确认
msg.Body 必须立刻拷贝,否则下一条就覆盖
msg.Body 是底层复用的 []byte,生命周期只到当前 for range 迭代结束。如果你把它直接扔进 goroutine 处理,大概率拿到的是空 slice 或前几条消息的残留内容。
- 安全拷贝写法:
data := append([]byte(nil), msg.Body)或data := make([]byte, len(msg.Body)); copy(data, msg.Body) - 不要把整个
msg结构体传进 goroutine:它的Ack/Nack方法不是线程安全的 - 耗时操作(DB 写入、HTTP 调用)必须放 goroutine,但确认动作必须回到原 goroutine 执行
QoS 和心跳不配,消费会悄无声息卡死
RabbitMQ 不会无限等你处理完。没设 QoS,它可能一口气发几十条消息过来;没配心跳,60 秒无响应就断连,而 Go 客户端默认不发心跳帧——结果就是连接断了你还不知道,ch.NotifyClose() 也没监听,消费彻底停摆。
- 必须在
ch.Consume()前调ch.Qos(1, 0, false):限制最多 1 条未确认消息,这是可靠性底线 - 连接层要启用心跳:
amqp.Config{Heartbeat: 30 * time.Second} - 监听
ch.NotifyClose()和conn.NotifyClose(),触发后重建连接和 channel - 别用
ch.IsClosed()判断状态——它只是本地缓存,不可靠
Kafka 场景下 ReadMessage 是调试陷阱
kafka-go 的 ReadMessage 看似简单,但它在读到消息后立即提交 offset。一旦业务 panic、网络超时或 DB 写失败,这条消息就丢了,且无法回滚。
- 生产环境必须用
FetchMessage+ 手动CommitMessages -
CommitMessages必须传 context 并检查返回 err,否则 offset 可能根本没提交 - 不能跨
kafka.Reader实例提交 offset,也不能在 goroutine 里对消息值拷贝调用它 - 多分区场景别硬扛,直接上
sarama.ConsumerGroup,自己实现 rebalance 和 offset 同步成本远高于引入成熟封装
真正难的从来不是连上队列,而是让 Ack 在正确时机、由正确 goroutine、对正确拷贝的数据执行——三者错一个,消息就进黑洞。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











