
Beego 里不能在 Controller 方法里直接调 ch.Consume() 启动消费者,否则 HTTP 请求会卡死、panic 风险高,且无法响应服务重启信号。
为什么 Beego 的 Controller 里不能写 ch.Consume()
ch.Consume() 返回的是一个阻塞的 ,一旦在 Beego 的 <code>Get() 或 Post() 方法里调用,当前 goroutine 就会永久挂起,HTTP 响应永远不返回。更危险的是,若 RabbitMQ 连接中断或队列不存在,ch.Consume() 可能直接 panic,拖垮整个 Beego 应用。
它不是“注册监听”,而是“立刻开始收消息并阻塞等待”。Beego 的 HTTP handler 必须快速返回,这点和 Gin 完全一致。
消费者必须跑在独立 goroutine + context 控制生命周期
正确做法是:在 Beego 应用启动时(比如 main.go 的 beego.Run() 之前),用 go 启一个长期运行的 goroutine,并传入 context.Context 监听退出信号(如 os.Interrupt 或 syscall.SIGTERM)。
- 消费者 goroutine 内部用
for msg := range msgs循环读取消息,别用select { case msg := 单次读取 - 收到
ctx.Done()后,先对所有未处理完的msg调msg.Nack(false, false)(拒绝且不重入队),再执行ch.Cancel()和conn.Close() - Beego 没有内置的优雅关机钩子,需手动在
os.Interrupt信号处理中触发ctx.Cancel()
msg.Ack() 必须显式调用,且要防 panic 和提前 return
AMQP 默认是手动确认模式(autoAck = false),消息送达后 RabbitMQ 会标记为 unacked,直到你调 msg.Ack(false) 才真正移除。漏掉 Ack 就会导致消息堆积、流控触发、吞吐暴跌。
常见漏 Ack 场景:
- 处理逻辑里
return早于Ack调用 -
process()抛 panic,没 recover 就跳过 Ack - 忘记加
defer或 defer 位置写错(比如 defer 在 if 外但 Ack 在 if 内)
稳妥写法是:
func handleMsg(msg amqp.Delivery) {
defer func() {
if r := recover(); r != nil {
msg.Nack(false, false)
}
}()
if err := process(msg.Body); err != nil {
msg.Nack(false, true) // 失败则重入队尾
return
}
msg.Ack(false) // 成功才确认
}
Beego 启动时初始化 RabbitMQ 连接池要防重复连接
Beego 的 app.conf 不支持直接注入 AMQP 连接,建议封装成单例,在 models/init.go 或 utils/rabbitmq.go 中实现:
- 用
sync.Once保证Connect()只执行一次 - 连接字符串从
beego.AppConfig.String("rabbitmq::url")读取,避免硬编码 - 不要在每个 Controller 里都
amqp.Dial()—— 连接数会爆炸,也浪费资源 - 生产环境务必设
amqp.Config{Heartbeat: 10 * time.Second},防止被 RabbitMQ 主动断连
Beego 本身不管理 consumer 生命周期,这部分完全得自己兜底;最容易被忽略的是:SIGTERM 到来时,consumer goroutine 是否真停了、有没有残留 unacked 消息、连接是否 clean close —— 这些不验证,上线后就会在凌晨三点收到告警。











