kratos本身不制造内存泄漏,但其组件和用法易放大泄漏点;pprof需显式注册路由或启动独立debug server,否则404;排查应聚焦业务代码在kratos生命周期中是否延长对象存活时间。

Kratos 框架本身不制造内存泄漏,但它的标准组件(如 transport/http、registry、middleware)和常见用法容易放大或掩盖泄漏点。排查核心不是“Kratos 有没有问题”,而是“你写的代码在 Kratos 的生命周期模型下,是否无意中延长了对象存活时间”。
pprof 启动后看不到 /debug/pprof 页面?
Kratos 默认不自动注册 pprof 路由,即使你 import 了 _ "net/http/pprof",如果没启动 HTTP Server 或用了自定义 http.ServeMux,接口也不会暴露。
- 确认是否已启动一个独立的 debug server:比如在
main.go中显式起一个http.ListenAndServe("127.0.0.1:6060", nil);nil是关键,它会让net/http/pprof自动挂载路由 - 若用了 Kratos 的
http.NewServer并传入了自定义http.ServeMux,必须手动注册:pprof.Handler("goroutine").ServeHTTP等,或升级到 Kratos v2.7+ 后调用pprof.RegisterHandlers(mux) - 检查监听地址:线上严禁用
0.0.0.0:6060,应绑定内网地址(如127.0.0.1:6060)并配合反向代理做访问控制 - curl 测试:
curl http://127.0.0.1:6060/debug/pprof/返回 HTML 列表才算成功;返回 404 说明注册失败,返回 403 说明有中间件拦截了/debug/路径
heap profile 显示 runtime.mallocgc 占比高,但找不到业务函数?
这是 Kratos 场景下最典型的误判——mallocgc 高只说明分配频繁,不代表泄漏;真正要看的是对象是否“长期驻留”。Kratos 的 middleware 链、context 传递、JSON 编解码都会高频分配,但多数是短生命周期。
- 别只看
inuse_space(默认),改用alloc_space:执行go tool pprof -sample_index=alloc_space heap.pprof,再top,找持续增长的分配源头 - 重点盯 Kratos 相关路径:
transport/http.(*Server).ServeHTTP、encoding/json.(*decodeState).object、middleware/tracing.(*Tracer).Trace—— 这些函数本身不泄漏,但可能暴露出你传进去的闭包捕获了大对象、或中间件里缓存了未清理的context.Value - 对比两次快照:用
go tool pprof --base base.heap.pprof current.heap.pprof,火焰图中红色部分才是新增驻留对象,绿色是释放掉的
goroutine 数量持续上涨,但 /debug/pprof/goroutine?debug=2 里全是 runtime.gopark?
Kratos 的 gRPC/HTTP Server 内部会复用 goroutine,但如果你在 handler 里启了子 goroutine 却没加 context 控制,就极易堆积。尤其注意 registry 和 discovery 模块的定时刷新协程。
- 检查所有
go func() { ... }()是否都绑定了父ctx,并用select { case 做退出守卫 - 警惕 Kratos 的
client封装:某些老版本http.Client或grpc.ClientConn在重试逻辑里会漏掉ctx透传,导致请求超时后 goroutine 仍卡在read系统调用上 - 用
go tool pprof http://127.0.0.1:6060/debug/pprof/goroutine?debug=2抓文本栈,搜索select、chan receive、time.Sleep,这些后面跟着的业务函数名就是泄漏入口点 - 第三方 registry 实现(如 etcd、nacos)的 watch 协程常被忽略:它们可能内部起了 goroutine 订阅变更,但没提供
Close方法或你没调用
最易被忽略的一点:Kratos 的 app.Run 会阻塞主线程,但很多开发者在 app.WithServers 之外又额外起了 goroutine 做后台任务(比如定时清理缓存),却忘了在 app.WithSignals 里注册优雅退出逻辑——这些 goroutine 在进程终止前根本不会收到停止信号,对象引用链就一直挂着。











