go中不存在全局panic捕获,recover仅对当前goroutine有效;http handler、子goroutine、定时任务等必须各自显式添加defer+recover,且需配合熔断器、shutdown等机制才能实现优雅容错。

Go 里没有“全局 panic 捕获”这回事——main 函数里的 recover 对 HTTP handler、子 goroutine、定时任务里的 panic 完全无效。 真正能起作用的,是把 recover 放在每个可能崩的执行入口点,再配合状态管理才能接近“优雅熔断”。
为什么 main 里 defer+recover 不管用
panic 是 goroutine 局部的,recover 只能在当前 goroutine 的 defer 中生效。HTTP 请求走的是独立 goroutine,后台任务用 go func() {...}() 启动也是新 goroutine,它们 panic 时 main 完全收不到信号。
- 现象:服务偶尔卡死、连接堆积、日志里没 panic 记录但请求超时
- 原因:handler 里某处 map nil 写入 panic → 协程静默退出 → 连接未关闭、资源未释放
- 关键点:
recover必须写在 defer 函数体内,且该 defer 必须在 panic 触发前注册(比如 handler 函数开头就 defer)
HTTP handler 中必须自己加 recover
以 Gin 为例,gin.Recovery() 只做两件事:把 panic 转成 500 响应 + 打印日志。它不记录失败次数、不切换状态、不降级,离“熔断”差很远。
一款AI开发辅助工具,主要用于通过后台进程将编码任务委托给 Codex、Claude Code 或 Pi 智能体。适用场景:(1)构建或创建新功能/应用,(2)审查 PR,适合需要提升相关任务效率的用户。
- 正确做法:在业务 handler 里手动 wrap
defer func(),并在 recover 后调用熔断器的MarkFailed() - 别依赖中间件兜底——中间件在 handler chain 末尾执行,如果 panic 发生在中间件之前(比如鉴权逻辑里),照样挂
- 示例中
breaker.Allow()要放在最前,避免请求进来后才检查状态
子 goroutine 必须单独防护
所有显式启动的 goroutine,包括 go func() {...}()、time.AfterFunc、worker pool 里的任务,都得自己包一层 defer+recover。
- 错误写法:
go riskyTask()——riskyTask里没 recover,崩了就丢 - 推荐封装:
goSafe(func() { ... }),内部统一 defer+recover+日志+错误上报 - 注意:recover 后不能继续用已损坏的变量,比如 slice 越界后 recover,那个 slice 已不可信
- 更稳妥的做法是用
errgroup.Group,它自带 panic 捕获和 cancel 传播
recover 后不该硬扛,而要协同 Shutdown
高频 panic 往往意味着服务已不稳定,此时继续响应请求只会雪上加霜。recover 不是万能膏药,而是止损起点。
- 在 recover 分支里触发
srv.Shutdown(),停止接收新请求,等待活跃连接完成 - 用
sync.Once控制只关一次,避免多个 panic 触发多次 shutdown 导致 panic - 绝对不要在 recover 里调
os.Exit()—— k8s 或 systemd 会当 crash 处理,反复拉起 - 真正复杂的地方在于:shutdown 期间如何拒绝新请求?需要配合反向代理(如 Nginx)健康检查或 service mesh 的流量控制










