http handler 中禁止 dial 和复用 channel;*amqp.connection 全局单例,*amqp.channel 按需创建并立即关闭;队列声明必须 durable:true 且 autodelete:false;publish 必须同步 confirm+超时;consumer 必须手动 ack+context 超时+单 goroutine 处理。

HTTP handler 里别 dial,也别复用 channel
在 Echo 的 HandlerFunc 里每次调用 amqp.Dial 或从全局连接反复取同一个 *amqp.Channel,不出三天就会遇到 connection refused 或 fatal error: concurrent map writes。RabbitMQ 连接是长连接,*amqp.Connection 必须全局单例;而 *amqp.Channel 是轻量但非并发安全的资源,必须按需创建、用完即关。
- 启动时用
sync.Once初始化*amqp.Connection,传入amqp.Config{Heartbeat: 10 * time.Second} - 每个 handler 执行任务发送前,调用
conn.Channel()拿新 channel,发完立刻ch.Close() - 绝对不要把 channel 存成 struct 字段或跨 goroutine 传递——哪怕只读也不行
- Docker/K8s 环境下,URL 中 vhost 必须 URL 编码,比如
amqp://user:pass@localhost:5672/%2F,写成/或空字符串会报ACCESS_REFUSED
队列声明必须 durable:true 且 autoDelete:false
用 ch.QueueDeclare("task_queue", true, false, false, false, nil) 声明队列时,durable:true 和 autoDelete:false 是硬性组合。漏掉前者,RabbitMQ 重启后队列消失,所有 DeliveryMode: amqp.Persistent 的消息变成“有家不能回”;设了后者,最后一个消费者断开,队列被自动删除,新实例启动直接 NOT_FOUND。
- 已存在的非持久化队列无法通过二次
QueueDeclare升级为 durable,必须先用rabbitmqctl delete_queue清理 - 如果业务需要多实例部署,
exclusive:false也必须显式设为 false,否则第二个实例连不上 - 队列名建议带服务前缀,比如
"order-service.task",避免不同服务误用同一队列
publish 必须同步 + confirm + 超时,不能丢给 goroutine
Echo handler 返回 HTTP 响应前,必须确保消息已进 RabbitMQ 队列(至少已发到 broker 并收到 ack)。用 go ch.Publish(...) 异步发,handler 返回后 goroutine 可能还没跑,更别说等确认——用户看到“提交成功”,实际消息卡在本地 buffer 里。
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
- 先调
ch.Confirm(false)开启确认模式,再ch.Publish,然后监听ch.NotifyPublish等 ack/nack - 超时控制在 500ms 内,超时则记录告警并返回
echo.HTTPError{Code: 503} - 消息体尽量提前序列化好,比如
json.Marshal(task)在 handler 内完成,避免Publish阻塞时间不可控 - 别依赖
Expiration字段做延迟——那是 TTL+DLX 方案用的,插件法要用Headers: amqp.Table{"x-delay": 30000}
消费者必须手动 Ack + context 超时 + 单 goroutine 处理 delivery
用 ch.Consume("q", "", false, false, false, false, nil) 启动消费时,第三个参数 autoAck 必须为 false。设成 true 等于告诉 RabbitMQ:“发完就删”,后续 panic、网络中断、甚至只是 time.Sleep(20*time.Second) 没执行完,消息就永久丢失。
- 每个
amqp.Delivery必须在接收它的同一个*amqp.Channel上调d.Ack(false)或d.Nack(false, true) - 启动 goroutine 处理 delivery 前,用
context.WithTimeout(ctx, 30*time.Second)包一层,防止 DB 查询或 HTTP 请求 hang 住 - 消费逻辑里关键字段(如
TaskID)必须校验非空,params别用map[string]interface{},改用具体 struct +json:"field_name"标签 - 重复投递是常态,订单类任务必须查 DB 状态再更新,不能只靠 if-else 判断
真正难的不是写通代码,是让每条 ch.Publish 都带 confirm,让每个 d.Ack 都在正确的 channel 上,让每个 handler 都不偷偷 dial —— 这些细节线上不会报错,只会静默丢消息。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










