每个消费者goroutine必须单独注册defer+recover,主线程的recover对其无效;panic后应记录日志并return退出,不可继续执行业务逻辑,同时需结合context或退出channel实现优雅终止。

recover必须在每个消费者goroutine里单独注册
主线程加了defer + recover,对worker协程里的panic完全无效。消息队列消费者通常是go func() { ... }()启动的独立goroutine,它们的panic是隔离的,不会传播到主goroutine。一旦某个消费者panic而没做防护,整个goroutine崩溃,消息可能丢失、连接未关闭、资源泄漏。
常见错误现象:日志里只看到“panic: runtime error: index out of range”,接着消费者goroutine静默退出,后续消息不再被处理,但服务进程还在跑——你以为它活着,其实它已经“半身不遂”。
- 每个
go启动的消费者函数开头,必须自己写defer func() { if r := recover(); r != nil { log.Printf("consumer panic: %v", r) } }() - 不要试图在main里统一注册一次
recover来覆盖所有worker——那只是幻觉 - 如果消费者封装成结构体方法(如
c.consumeLoop()),recover要放在该方法内部,而不是调用它的外层函数里
recover后不能继续使用已破坏的map/slice/指针字段
消息消费逻辑常涉及解析JSON、更新状态map、批量写入数据库等操作。一旦发生panic("concurrent map writes")或panic("invalid memory address"),底层数据结构很可能已损坏。此时recover()能拦住崩溃,但不代表你能安全地继续执行后续业务逻辑。
典型踩坑场景:消费者从channel读出一条消息,解析失败导致panic;recover后你仍尝试db.Save(msg)或statsMap[msg.Type]++——这些操作可能二次panic,或者写入脏数据。
- 推荐模式:
if r := recover(); r != nil { log.Printf("drop msg, panic: %v", r); return }——记录即退出,不续跑 - 若需清理(如关闭临时文件、释放锁),应提前注册在
defer链中,而非写在recover分支里 - 避免在
recover后调用close(ch)或向刚panic过的channel发送信号——channel可能已被关闭或阻塞
结合channel退出信号做优雅终止
单纯recover只能防止单次panic导致goroutine退出,但无法解决“反复panic → 反复重启 → 消息积压”的恶性循环。真实消息队列消费者需要配合退出控制,比如收到ctx.Done()或shutdownCh时主动停止拉取消息。
否则会出现:某条消息触发panic → recover拦截 → 函数return → goroutine结束 → 无人重启它 → 消费能力永久下降。
- 消费者goroutine应监听
context.Context或专用退出channel,在recover后检查是否该退出,而不是无条件重试 - 示例结构:
go func() { defer func() { if r := recover(); r != nil { log.Printf("panic recovered: %v", r) } }() for { select { case msg := - 不要在recover后直接
continue进入下一轮循环——那等于把问题消息丢进黑洞,且掩盖了根本原因
recover返回值类型断言前必须判空
消费者遇到panic(errors.New("timeout"))和panic("bad json")时,recover()返回的r类型完全不同。直接r.(error).Error()会引发二次panic,导致本该被拦截的错误再次崩掉goroutine。
线上日志里出现“panic: interface conversion: interface {} is string, not error”,基本就是这个原因。
- 永远先检查
r != nil,再做类型判断 - 安全打印用
fmt.Sprintf("%v", r);结构化日志建议用switch r.(type)分支处理 - 不要假设所有panic都带
error——第三方库、运行时错误、甚至panic(42)都合法
recover语法,而是判断哪条消息该丢、哪条该重试、哪些状态已不可信。很多团队卡在“recover写了,但panic还是频发”,问题往往不在recover本身,而在上游消息校验缺失、消费者幂等性没做、或错误分类太粗放。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











