视频上传接口返回后rabbitmq消息总丢,根本原因是http handler生命周期极短,响应返回后连接可能被复用或关闭,而amqp.channel.publish是同步阻塞调用,此时若channel已关闭或网络异常,会panic或静默丢任务;正确做法是handler仅序列化任务并非阻塞投递至带缓冲channel,由独立goroutine负责amqp发送、重试与连接恢复。

不能在 Gin 的 Handler 里直接调用 amqp.Channel.Publish 发送转码任务——HTTP 响应一返回,连接可能就被复用或关闭,此时发消息会 panic 或静默丢任务。
为什么视频上传接口返回后,RabbitMQ 消息总丢?
根本原因是 Go 的 HTTP handler 生命周期极短:w.WriteHeader() 或 w.Write() 返回后,net.Conn 就可能被 http.Server 回收。而 RabbitMQ 的 amqp.Channel.Publish 是同步阻塞调用,若此时网络抖动、channel 已 close 或 RabbitMQ 拒绝连接,就会触发 panic: send on closed channel,或者直接丢弃 payload,毫无日志和可观测性。
常见错误包括:
-
defer ch.Close()放在 handler 末尾,但 channel 实际已在响应写出后被回收 - 复用已关闭的 channel(没检查
ch.IsClosed())导致 panic - 用全局单例
*amqp.Channel被多个 goroutine 并发写入——AMQP 协议不允许多写
如何让短视频上传接口毫秒级返回,又不丢转码任务?
核心原则:Gin handler 只做两件事——序列化任务 + 非阻塞投递到带缓冲 channel;真正的 AMQP 发送必须由独立 goroutine 完成,且自带重试、连接恢复、错误分类处理能力。
实操建议:
RabbitMQ 4.2.3 是 2026 年初发布的重要稳定更新版本,重点修复了 Khepri 元数据存储相关问题,并改进了监控性能。对于使用 Docker、Kubernetes 或微服务架构的开发团队来说,该版本兼容性和稳定性表现较好。
- 声明全局带缓冲 channel:
var taskCh = make(chan []byte, 2000)(按峰值 QPS × 平均转码延迟预估,比如 500 QPS × 4s = 2000) - handler 中只做非阻塞投递:
select { case taskCh - 单独 goroutine 消费:
go func() { for payload := range taskCh { publishToRabbitMQ(payload) } }() -
publishToRabbitMQ必须每次新建amqp.Publishing实例,避免headers复用污染 - 必须显式捕获
amqp.Error类型(如NOT_FOUND队列不存在、PRECONDITION_FAILED),不能直接panic或忽略
消费者端手动 ACK 总失败,转码任务卡死?
不是 RabbitMQ 不给 ACK,是你根本没发出去。最常见原因是业务逻辑里出现 return、panic 或未 recover 的 error,导致 msg.Ack(false) 根本没执行。RabbitMQ 看不到 ACK,就把消息一直锁在 unacked 状态。积压到一定数量会触发流控(flow control),整个 channel 被 block,新消息进不来,队列就卡死了。
必须做到:
-
msg.Ack(false)放在defer里,且defer前确保msg != nil - 业务逻辑包在
defer func() { if r := recover(); r != nil { msg.Nack(false, true) } }()中 - 不要用
msg.Ack(true)(multiple=true),它会批量确认前面所有未 ack 的消息,极易造成消息丢失 - FFmpeg 转码失败时,必须
msg.Nack(false, false)(不重入队列),并路由到死信交换器(DLX)供人工介入
视频转码任务需要幂等和顺序保障吗?
短视频上传场景下,**顺序不关键,但幂等必须**。用户重复上传同一视频 ID,不能触发两次转码;同一视频多次触发转码请求,最终只应生成一份目标文件。
实现方式:
- 生产者侧:在
amqp.Publishing.Headers中写入video_id和task_id(UUID),用于下游去重 - 消费者侧:转码前先查 Redis,
SETNX video_id:processing 1 EX 300;若已存在则直接Ack并跳过 - FFmpeg 执行后,用
os.Stat校验OutputPath是否已存在且大小非零,避免覆盖成功结果 - 不依赖 RabbitMQ 的消息顺序保证——工作队列(Work Queues)模型本身是轮询分发,无全局顺序语义
真正容易被忽略的点是:消费者进程退出前,必须主动关闭 channel 并等待 unacked 消息超时释放,否则 RabbitMQ 会认为 worker 还活着,持续把新消息派发过去,而实际没人处理。










