http handler 必须在每个 goroutine 内部 defer recover,而非仅在 main 中;推荐用 recovery 中间件统一处理,并注意子 goroutine、第三方回调、熔断状态同步及 panic 后资源清理与状态可信性。

单纯在 main 函数里加 defer + recover,对 HTTP 接口完全无效——每个请求都在独立 goroutine 中执行,panic 发生在那里,就得在那里捕获。
HTTP handler 必须自己 defer recover
Go 的 HTTP server 启动一个新请求时,会用 go srv.ServeConn(c) 这类方式派发到新 goroutine。这意味着:你写在 main 里的 recover 和 handler 完全不在同一个协程里,根本收不到 panic。
- 正确位置:每个
http.HandlerFunc开头就注册defer func() { if r := recover(); r != nil { /* 记录、返回500 */ } }() - 更推荐:封装成中间件,比如
func Recovery(next http.Handler) http.Handler,统一注入到路由链中 - 别漏掉:WebSocket 升级后的连接处理函数、长轮询 handler、文件上传回调等非标准路径,它们也各自开 goroutine
- 注意顺序:
defer必须在可能 panic 的代码之前注册;如果 handler 里先调了第三方库再 defer,而库 panic 了,就来不及捕获
goroutine 泄漏比 panic 更危险
recover 只是让当前 goroutine 不崩,但它不清理资源。高频 panic 往往伴随 goroutine 泄漏——比如 panic 前已启动子 goroutine、已发送 channel、已打开文件但没 close。
- recover 后必须显式清理:关闭
http.ResponseWriter关联的连接(实际不可关,但要确保不继续写)、释放数据库连接、close()已创建的 channel - 禁止在 recover 块里继续执行业务逻辑:比如 panic 后还去调
db.Query或发 HTTP 请求,极大概率二次 panic - 用
debug.Stack()记录完整堆栈,别只打r(它只是 panic 参数,不含调用链) - 生产环境避免
log.Printf,改用结构化日志(如zap),字段带上request_id和stack
跨 goroutine panic 必须各自兜底
你在 handler 里起一个 go func() { riskyParse() }(),这个子 goroutine 的 panic 永远不会传到 handler 的 recover 里——Go 不支持跨协程 panic 传播。
- 所有显式
go启动的函数,开头第一行必须是defer func() { recover() }() - 用带缓冲 channel 统一收集错误:
panicCh = make(chan error, 1000),避免日志 goroutine 阻塞导致主流程 hang - 不要在子 goroutine 里调
os.Exit()或panic后什么都不做——它会静默消失,无日志、无监控、无告警 - 第三方库的异步回调(如 Kafka consumer、Redis pub/sub listener)同样需要独立 recover,不能依赖上层
recover 不是熔断,状态管理必须手动做
recover 捕获 panic 后,如果不联动熔断器,下一次请求照常进来,照样 panic——这叫“假高可用”。真正的防崩溃,是快速失败 + 状态切换。
- 每次
recover成功后,必须立刻调breaker.MarkFailed()(如gobreaker)或hystrix.MarkFailed() - 入口处强制检查:
if !breaker.Allow() { http.Error(w, "service unavailable", http.StatusServiceUnavailable); return } - 熔断器状态变量(
Closed/Open/Half-Open)必须用sync.RWMutex或atomic.Value保护,否则并发修改会导致状态错乱 - 别用
hystrix.Do:它内部不 recover,panic 会直接冒泡;改用hystrix.DoC,它才做兜底并转成可判断的*hystrix.Error
最易被忽略的一点:recover 后的程序状态大概率已损坏,比如 map 已被并发写 panic、channel 已 close、事务已部分提交。此时强行“恢复执行”比直接返回错误更危险——高可用的前提是结果可信,不是表面不挂。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











