beego 中 rabbitmq 必须用 sync.once+重试初始化、consumer 独立 goroutine 运行于 appinit 阶段、每 consumer 独占 channel、日志带 msg.messageid 和 delivery_tag。

Beego 框架本身不提供 RabbitMQ 封装,直接调 amqp.Dial() 或在 Controller 里启 Consumer,十有八九会 panic、丢消息、日志串行、ACK 失败——这不是配置问题,是生命周期错配。
不要在 main() 或 init() 里裸调 amqp.Dial()
Beego 的 app.Run() 是阻塞调用,如果网络未就绪、DNS 解析失败或 RabbitMQ 服务暂不可达,amqp.Dial() 会直接 panic,整个服务起不来。
- 改用
sync.Once+ 带重试的初始化函数,例如每 3 秒重试一次,最多 5 次,超时设为 5s - 从
beego.AppConfig.String("rabbitmq::url")读取连接串,别硬编码 - 连接失败时仅记录
warn日志,设置全局状态rabbitmq.status = "unavailable",HTTP 服务照常启动 - 避免用
time.Sleep等待连接,应使用context.WithTimeout控制 Dial 超时
Consumer 必须脱离 HTTP 生命周期独立运行
在 Controller.Get() 里调 ch.Consume() 并 for-range 消费,等同于把消费者 goroutine 绑定到一次 HTTP 请求上。请求一结束,goroutine 被回收,msg.Ack() 永远发不出去,消息反复重回队列。
- Consumer 启动时机应在
beego.AppInit()阶段,用go func() { ... }()启动长任务 - 每个 Consumer 应独占一个
*amqp.Channel,禁止多个 goroutine 共享 channel(AMQP channel 不是并发安全的) - ACK 必须由该 goroutine 自己完成,不能跨 goroutine 调用或依赖 controller 实例
- 若需回传处理结果(如更新订单状态),走
sync.Map、本地chan struct{}或 Redis,而非试图调用 controller 方法
日志必须带消息上下文,否则查无可查
Beego 默认日志无 trace ID,而 RabbitMQ 每条 amqp.Delivery 都有 msg.MessageId 和 msg.DeliveryTag。多 consumer 并发时,log 交织,根本分不清哪条 log 对应哪条消息。
- 在
for range msgs循环开头立即构造结构化日志:log.WithFields(log.Fields{"msg_id": msg.MessageId, "delivery_tag": msg.DeliveryTag}) - 禁用
beego.BeeLogger.SetLevel(logs.LevelDebug)全局开关;对 RabbitMQ 模块单独启用 debug 级别 - 绝对不用
fmt.Printf打消费日志——它绕过 Beego 日志系统,无法统一采集和分级 - 建议在消息处理入口打
INFO,成功后打INFO,panic 或 recover 时打ERROR,并带上msg.ReplyTo和msg.CorrelationId(如有)
最易被忽略的是 Channel 并发安全性:AMQP 协议规定 channel 是单线程语义,ch.Publish() 和 ch.Consume() 不能混用同一个 channel,更不能在多个 goroutine 里并发调用。生产者、消费者、声明 queue/exchange 应各用独立 channel,且复用而非频繁新建——channel ID 空间有限(默认 2047),耗尽后会报 channel id space exhausted。











