Go应用应全局复用RabbitMQ连接,HTTP handler中禁用amqp.Dial;需设队列durable、消息Persistent并启用Confirm模式;publish须同步带超时;消费者需单goroutine处理Ack/Nack并监听NotifyClose。

Go 语言本身没有“官方框架”强制绑定 RabbitMQ,但用 github.com/streadway/amqp 配合 Gin、Echo 或纯 net/http 就能快速搭出生产级调度系统——关键不是选什么框架,而是怎么组织连接、channel 和任务生命周期。
为什么别在 HTTP handler 里直接 Dial RabbitMQ
每次 HTTP 请求都 amqp.Dial 会迅速耗尽文件描述符,且连接建立开销大,容易触发 connection refused 或 too many open files。RabbitMQ 连接是长连接,应该全局复用。
- 启动时调用一次
amqp.Dial,保存*amqp.Connection到全局变量或依赖注入容器中 - 每个请求需要发任务时,从该连接调用
Channel()获取新*amqp.Channel,用完立刻ch.Close() - 不要复用
*amqp.Channel跨 goroutine —— 它不是并发安全的,多处写入会 panic:fatal error: concurrent map writes
如何让任务消息不丢:持久化 + 确认模式必须配齐
默认情况下,amqp.Publishing 发出去的消息既不持久化、也不等待 broker 确认,断电或 broker 重启后全丢。要保证至少一次(at-least-once)投递,两件事缺一不可:
- 声明队列时设
durable: true:ch.QueueDeclare("task_queue", true, false, false, false, nil) - 发消息时设
DeliveryMode: amqp.Persistent,且开启 channel 的确认模式:ch.Confirm(false),再用ch.Publish后监听ch.NotifyPublish等待 ack - 若收到 nack,需重试或落库补偿;忽略 nack 就等于放弃可靠性
Gin/Echo 中异步发任务的典型陷阱
HTTP handler 返回前必须确保任务已进队列(或至少已发到 RabbitMQ),否则用户以为提交成功,实际消息卡在本地 buffer 里。
立即学习“go语言免费学习笔记(深入)”;
- 别用
go ch.Publish(...)直接丢进 goroutine —— handler 返回后 goroutine 可能还没执行,更别说等 ack - 正确做法:handler 内同步调用带 confirm 的 publish,超时控制在 500ms 内;超时则记录告警并返回 503
- 如果任务构造复杂(如查 DB、序列化大结构体),可先在 handler 内完成构造,再同步 publish 序列化后的
[]byte,避免阻塞时间不可控
消费者端用 channel 处理并发时的常见崩溃点
很多人用 ch.Consume 拿到 <-chan amqp.Delivery 后,直接起多个 goroutine 消费,结果遇到 amqp: channel error 或静默丢消息。
-
amqp.Delivery.Ack/Nack必须由同一个*amqp.Channel实例调用,不能跨 goroutine 混用 - 推荐模式:一个 channel 对应一个消费 goroutine,内部用
for d := range msgs循环,每个d在本 goroutine 内完成处理 + Ack/Nack - 若需并发执行任务逻辑,把
d的 body 和 delivery tag 复制出来,另起 goroutine 处理,但d.Ack()仍由原 goroutine 调用(用 channel 回传结果或 tag)
最易被忽略的是 channel 的错误监听 —— ch.NotifyClose 和 conn.NotifyClose 必须接住,否则连接意外断开时,后续所有 Publish 或 Consume 都会 panic,而日志里只有一行 invalid memory address。


















