Kratos服务协程泄漏需盯runtime.NumGoroutine()和/debug/pprof/goroutine?debug=2:前者看压测后是否不回落,后者查chan receive等阻塞堆栈;常见于HTTP client未读响应体、中间件goroutine未监听ctx.Done()、time.Ticker未stop、gRPC流未close。

Kratos 框架本身不制造协程泄漏,但它是基于 Go 构建的,所有协程泄漏问题都来自你写的业务代码、中间件或第三方客户端调用——排查方法和原生 Go 完全一致,只是入口和可观测性更集中。
怎么快速确认 Kratos 服务存在协程泄漏
别看日志、别猜接口,直接盯住两个数字:
-
runtime.NumGoroutine():在 Kratos 的健康检查接口(如/healthz)里打点返回当前值,压测前后对比。单次请求后不回落、空闲态持续爬升,就是泄漏信号 -
/debug/pprof/goroutine?debug=2:Kratos 默认已集成_ "net/http/pprof",只要启用了 HTTP server 就能访问。重点扫chan receive、select、io.ReadFull、net.Conn.Read这几类状态,出现阻塞时长超 300s(比如432000s)基本可断定是泄漏
注意:?debug=1 只给总数和分类统计,看不出谁卡在哪;必须用 ?debug=2 才能拿到完整堆栈。
Kratos 常见泄漏场景及对应代码特征
Kratos 的结构让某些泄漏模式特别高频,尤其集中在 middleware、client 调用和定时任务里:
- HTTP client 响应体未读完也未关闭:调用
http.DefaultClient.Do()或自定义http.Client后,只取了resp.StatusCode就扔掉resp,底层连接池无法复用,readLoop/writeLoopgoroutine 卡死 - Middleware 里启了 goroutine 但没绑定
ctx:比如在func(ctx context.Context, req interface{}) (interface{}, error)中起 goroutine 处理日志或埋点,却忘了监听ctx.Done(),请求结束时它还在等 channel 或 sleep - 使用
time.Ticker或time.AfterFunc未显式 stop:Kratos 的app.Run()结束时不会自动清理这些定时器,goroutine 持续存活 - gRPC client 流式调用未 close send/receive:比如
stream.SendMsg()后没调stream.CloseSend(),或stream.Recv()在 for 循环里没处理io.EOF就 break,导致流 goroutine 悬停
怎么在 Kratos 项目里加 goleak 自动拦截
goleak 是目前最实用的测试期协程泄漏守门员,但在 Kratos 项目里容易误报或漏报,关键在初始化时机:
- 不要在每个 test 文件里写
defer goleak.VerifyNone(t):Kratos 的app.Init()或dao.New()可能提前启了后台 goroutine(比如 etcd watcher、prometheus metrics collector),verify 会误判 - 正确做法:在
TestMain里用goleak.VerifyTestMain(m)包裹整个测试流程,并确保所有后台资源(如app.Stop()、dao.Close())在m.Run()后被显式释放 - 若测试中手动启了 goroutine(如模拟异步回调),必须加同步机制:
done := make(chan struct{}),启动后go func() { ...; close(done) }(),再等它结束,再触发 verify
示例关键行:go func() { defer close(done); time.Sleep(10 * time.Millisecond) }() —— 不要依赖 time.Sleep 等待,要用 channel 显式同步。
为什么 Kratos 的 pprof 端点有时看不到泄漏 goroutine
不是工具失效,而是 Kratos 的多实例/多 server 架构导致调试端口不统一:
- 默认 pprof 注册在 HTTP server(如
8000端口),但如果你同时起了 gRPC server 或其他 HTTP server(如 metrics server 在9000),pprof 可能只挂在其中一个上 - 检查你的
app.New()配置,确认server.HTTP是否启用了 pprof:有则访问http://localhost:8000/debug/pprof/goroutine?debug=2;没有则查server.Metrics对应的端口 - 如果用了
kratos transport的多路复用(如 HTTP+gRPC 共享 listener),pprof 可能被路由规则拦截,此时需临时改配置,单独暴露一个纯 debug server
最稳妥的方式:启动时加 GODEBUG=goleak=1 环境变量,再访问 /debug/pprof/goroutineleak?debug=1(Go 1.24+ 支持),它比传统 pprof 更聚焦“不可达阻塞”,但只捕获向已关闭 channel 发送、所有 select case 均不可达这类明确泄漏,对死循环无效。
真正难缠的泄漏往往藏在 client 初始化、后台 watch 逻辑、或第三方 SDK 的隐式 goroutine 里——它们不会出现在 pprof 堆栈顶部,但会钉住大量内存。盯紧 NumGoroutine 曲线 + ?debug=2 快照对比,比任何工具都管用。











