Go worker协程崩溃后不会自动重启,recover仅对当前goroutine有效且须在defer中调用;需手动封装safeGo函数,结合defer+recover、日志、堆栈、退避重启循环实现可控自愈。

Go 的 worker 协程崩溃后不会自动重启,recover 也救不了它——除非你提前在每个 worker 入口手动加了 defer + recover,并且设计了明确的重启逻辑。否则 panic 一发生,协程就静默退出,吞吐量直接掉点,还查不到原因。
worker 崩溃时为啥 recover 不起作用
很多人把 recover 放在 main 函数里,以为能兜住所有 goroutine。这是错的:recover 只对当前 goroutine 有效,且必须在 defer 中调用。一个裸写的 go func() { panic("x") }(),主 goroutine 根本无感,进程也不会退出,但那个 worker 就永远消失了。
-
recover不是全局钩子,也不是“panic 拦截器”,它只是当前 goroutine 的局部逃生舱口 - worker 启动后若没包
defer func() { recover() }(),panic 就直接终止该 goroutine,不报错、不打日志、不通知任何人 - 如果 worker 是循环任务(比如
for { doWork(); time.Sleep(...) }),panic 后整个 for 循环就断了,后续不再执行
如何让 worker 真正自愈:safeGo + 显式重启循环
推荐封装一个 safeGo 工具函数,把 recover、日志、堆栈、重启节奏全收口。关键不是“捕获就完事”,而是捕获后决定是否重启、何时重启、重启前要不要清理。
- 用
runtime.Stack获取完整调用栈,而不是只记err字符串,否则线上根本没法定位 - 重启不能写在
recover的 defer 里直接go safeGo(...)—— 容易无限 fork,压垮调度器 - 正确做法是:recover 后记录错误 →
return退出当前 goroutine → 由外层循环(或状态机)控制是否重试、退避、降级 - 示例结构:
func runWorker(name string, fn func()) {
for {
defer func() {
if r := recover(); r != nil {
buf := make([]byte, 4096)
n := runtime.Stack(buf, false)
log.Printf("[PANIC] %s: %v\n%s", name, r, buf[:n])
time.Sleep(time.Second) // 退避
}
}()
fn()
}
}
然后启动:go runWorker("inventory-sync", doInventorySync)。这样每次 panic 后,goroutine 自动 sleep 1 秒再重进循环,既防雪崩,又保吞吐。
立即学习“go语言免费学习笔记(深入)”;
吞吐敏感场景下的三个硬约束
高吞吐 worker(如消息消费、实时计算)对重启延迟、失败传播、资源泄漏更敏感,不能靠“重启万能论”。
- 别在
recover里做耗时操作:发 HTTP 告警、写磁盘日志、连 DB —— 这些会卡住 goroutine 调度,拖慢整体吞吐 - 重启前检查上下文:
if ctx.Err() != nil { return },避免在服务正在 shutdown 时强行拉起新 worker - worker 内部若有长连接、channel、timer,panic 后它们不会自动关闭;必须在
fn()开头或recover后显式清理,否则内存/句柄缓慢泄漏
systemd 或 k8s 才是真正的兜底,不是 Go 自己
哪怕你把每个 worker 都包得严严实实,也扛不住 OOM、SIGKILL、硬盘满、cgroup 限流超限这些系统级崩溃。这时候 recover 完全无效,唯一可靠的是外部守护。
- 用
systemd时,Restart=on-failure+RestartSec=3+StartLimitBurst=2是底线配置,防止反复崩溃打满日志 - k8s 场景下,
LivenessProbe必须指向真实业务健康端点(如/health?deep=true),不能只 ping 端口 —— 否则 worker 卡死但进程还在,k8s 认为它“活着” - Go 进程退出码必须有意义:正常退出用
os.Exit(0),panic 后建议os.Exit(1),让守护器知道这不是 graceful shutdown
真正影响吞吐的,往往不是单次 panic,而是 panic 后没清理资源导致后续 worker 陆续失败,或者守护器因配置不当反复拉起又杀掉。自愈不是“让它活下来”,而是“让它死得明白、重启得克制、恢复得可控”。


















