recover不能让goroutine“起死回生”,仅防止单个http请求拖垮服务;必须在handler首行用defer func(){recover()}()直接调用,不可包层或延迟注册,recover后须立即返回、不操作损坏状态,并记录结构化日志。

recover 不能让崩掉的 goroutine “起死回生”,它只负责让当前 HTTP 请求不拖垮整个服务——用错位置、跨协程、或 recover 后继续操作已损坏状态,反而会让问题更隐蔽。
HTTP handler 里必须在开头注册 defer + recover
panic 发生在中间件之后、业务逻辑深处时,如果 defer 没提前注册,就根本捕获不到。Go 的 HTTP server 是每请求一个 goroutine,每个 handler 必须自己防护。
- 正确写法:
defer func() { if r := recover(); r != nil { /* 记录日志 + 返回 500 */ } }()必须放在http.HandlerFunc第一行 - 错误写法:把
defer写在某个子函数里(比如processRequest()),或者放在if err != nil分支之后 - 别依赖“全局中间件”——
http.HandleFunc注册的是函数值,不是引用;每个 handler 都得手动加
recover() 必须在 defer 匿名函数里直接调用
这是最常失效的原因:recover 不是普通函数,它只在 defer 执行流中、且是“直接调用”才有效。包一层就断链。
- ✅ 有效:
defer func() { r := recover(); if r != nil { ... } }() - ❌ 失效:
func handleRecover() { recover() }; defer handleRecover()—— 此时recover()在普通函数里执行,永远返回nil - ❌ 失效:
defer recover()—— 这会在注册时立刻执行,panic 还没发生,自然捕获不到
recover 后别碰已破坏的数据结构
recover 能止住 panic,但不会修复底层状态。数组越界后 slice 可能已损坏,map 并发写入后内部哈希表可能崩溃,此时再读写会二次 panic。
- recover 成功后,应立即返回,不要继续执行后续业务逻辑
- 资源清理(如关闭文件、释放锁)要靠
defer链本身完成,而不是写在recover()后面的 if 块里 - 别对 panic 值做未经检查的类型断言:
r.(error).Error()会再次 panic;先判断r != nil,再用switch r.(type)或fmt.Sprintf("%v", r)
生产环境要记录结构化 panic 日志,不能只打印字符串
仅用 fmt.Println(r) 或 debug.PrintStack() 会丢失关键上下文:goroutine ID、HTTP 路径、请求 ID、时间戳。这些对定位线上偶发 panic 至关重要。
- 推荐用
zap等结构化日志库,把r、堆栈、req.URL.Path、req.Header.Get("X-Request-ID")一起打点 - 务必调用
http.Error(w, "", http.StatusInternalServerError),否则客户端收不到响应,连接会 hang 住 - 避免在 recover 块里启动新 goroutine 做异步上报——万一它又 panic,没人兜底
真正难的不是写对那几行 defer func() { recover() },而是判断哪些 panic 真该 recover,哪些其实该提前用 if 检查并返回 error。越往业务层走,越该靠防御性编程,而不是靠 recover 擦屁股。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











