消费端必须独立于http服务进程启动,使用goroutine运行consumemsgwork并做好错误隔离;需手动幂等应答消息,确保队列与消息持久化;不可复用gin的上下文、日志或配置,应全局初始化共享资源。

消费端必须独立于HTTP服务进程启动
Gin本身是HTTP服务器框架,不负责长期运行的后台任务。把 ConsumeMsgWork 直接塞进 main() 里、和 r.Run() 同时跑,看似简单,实则危险:一旦HTTP请求触发 panic,整个进程(含消费者)会退出;反之,RabbitMQ连接异常或重连失败,也可能拖垮Web服务。
正确做法是让消费逻辑在独立 goroutine 中启动,并做好错误隔离:
- 用
go ConsumeMsgWork("que01", "ui-service")启动,不要阻塞r.Run() - 在
ConsumeMsgWork内部加死循环 +time.Sleep退避,避免连接失败后疯狂重试 - 所有 AMQP 错误(如
amqp.ErrClosed、io.EOF)必须捕获并重建 channel 和 connection,不能 panic 或 return - 不要依赖 Gin 的中间件或上下文(
*gin.Context),消费端没有 HTTP 生命周期
消息应答必须手动控制且幂等处理
RabbitMQ 默认自动应答(autoAck=true)极不安全——消费者进程崩溃前没来得及处理完消息,消息就永久丢失了。你看到的 ConsumeMsgWork 里设了 false 是对的,但后续没做应答,会导致消息堆积、重复投递甚至队列阻塞。
关键动作只有两步,缺一不可:
- 业务逻辑成功执行后,调用
msg.Ack(false)确认消费(false表示不批量确认) - 业务出错时,根据场景选:
msg.Nack(false, true)(重新入队,可能被重复消费)或msg.Reject(false)(丢弃/进死信队列) - 数据库写入失败、HTTP回调超时等都算业务失败,不能跳过应答逻辑
- 务必在业务逻辑最外层用
defer包一层应答兜底,防止 panic 导致未应答
队列与消息需持久化,且消费者要声明一致
你代码里 QueueDeclare 的 durable=true 参数是对的,但这只是“队列存在”层面的持久化。真正决定消息不丢的是三个地方必须全部开启:
- 队列声明时
durable=true(你已做) - 发布消息时
DeliveryMode: amqp.Persistent(你已做) - 消费者端
QueueDeclare的参数必须和生产者完全一致(尤其是durable、autoDelete、exclusive),否则 RabbitMQ 会拒绝声明并报错channel error: queue 'que01' in vhost '/' refers to the same name but different properties - 别忽略
Exchange类型——工作队列模式下用默认 exchange(空字符串),就别在Publish里传非空exchange名
消费端无法复用 Gin 的日志/配置/DB 实例
Gin 的 gin.Engine、中间件、gin.Context 都绑定在 HTTP 请求生命周期内,消费端是长连接后台协程,既无 request,也无 response。强行注入会导致 panic 或数据竞争。
替代方案很直接:
- 日志用独立 logger(如
log或zerolog),别用gin.DefaultWriter - 数据库连接池(
*gorm.DB)、Redis 客户端(*redis.Client)应在main()初始化后全局导出,供消费端直接使用 - 配置项(如 RabbitMQ 地址、重试次数)统一从
config.json或环境变量读取,不要通过 Gin 的c.MustGet()传递 - 若需链路追踪,用 OpenTelemetry 的
context.Background()+ 自定义 trace ID,而非复用 HTTP 上下文











