Kratos 框架本身不导致 CPU 飙高,问题多源于业务代码空转、死循环、高频重试或同步阻塞;应通过 pidstat -t 定位高 CPU 线程,结合 gdb 和 pprof 分析 goroutine 堆栈及热点函数。

Kratos 框架本身不直接导致 CPU 飙高,问题几乎都出在业务代码、中间件调用或配置失当上。定位关键不是“Kratos 有没有 bug”,而是“你的 http.Server 或 grpc.Server 里哪个 handler 在空转、死循环、高频重试或同步阻塞”。
看 pidstat -t 找高 CPU 线程,别只盯 top 进程总值
Kratos 默认使用 Go runtime,一个进程内可能有上百 goroutine,但 OS 层看到的是 M:N 映射后的线程(pthread)。top 显示的 %CPU 是整个进程累加值,掩盖了单个线程打满一个核的情况。
- 执行
pidstat -t -p $(pgrep -f 'your-kratos-binary') 1,观察%CPU列持续 >90 的TID - 用
printf "%x\n" <TID>把线程 ID 转成十六进制,再用gdb -p <PID> -ex 'thread apply all bt' -ex quit 2>/dev/null | grep -A5 -B5 <hex-tid>定位该线程正在执行哪段 Go 代码 - 特别注意
runtime.futex、runtime.epollwait后面紧跟着的业务函数——那往往是阻塞等待后刚被唤醒、立刻又进入下一轮忙等的位置
查 pprof /debug/pprof/profile 看热点函数,重点扫 for { select {} } 和 time.Sleep(0)
Kratos 项目默认启用 pprof,但很多团队没暴露 /debug/pprof 路由,或只开了 /metrics。没有火焰图,你大概率会漏掉「看似合法实则危险」的写法。
- 确认服务启动时注册了
pprof.Register(),且 HTTP server 路由包含"/debug/pprof/"(注意末尾斜杠) - 用
wget "http://localhost:8000/debug/pprof/profile?seconds=30" -O cpu.pprof抓 30 秒 CPU 样本 -
go tool pprof cpu.pprof进入交互后,输入top看前 10 函数;输入web生成调用图;重点关注:runtime.selectgo上游是不是你的for { select { case - 常见坑:
grpc.Dial失败后无退避地重试、redis.Client.Get超时设为 0、自定义 middleware 里用for !ready { time.Sleep(0) }等待初始化完成
检查 Kratos 中间件链里有没有同步日志/JSON 序列化/鉴权穿透逻辑
Kratos 的 middleware 是按顺序串行执行的,任何中间件里的 CPU 密集操作都会放大 N 倍(N = QPS × 并发数)。尤其要注意那些「看起来只是打个日志」的中间件。
- 禁用所有自定义中间件,只留
transport.GRPCServer或transport.HTTPServer默认链,观察 CPU 是否回落——如果回落,问题就在中间件 - 逐个启用中间件,配合
perf record -e cycles,instructions,cache-misses -p <PID> sleep 10看指令数突增点 - 典型高开销操作:
zap.String("body", string(reqBody))(大请求体转字符串)、json.Marshal在 middleware 里序列化完整 context、用jwt.Parse解析未缓存的 token 每次都验签 - Kratos 内置
logging.Server中间件默认会记录请求体,务必确认logReqBody设为false,或用log.WithFields()替代字符串拼接
确认 kratos run 启动参数和 Go runtime 设置没误配
有些团队为了“压测更猛”,手动改了 GOMAXPROCS 或加了 -gcflags="-l",结果让 GC 更频繁、逃逸分析失效、小对象堆分配暴增——这些都会抬高 CPU。
- 检查启动命令是否含
GOMAXPROCS=1或极小值:这会导致调度器争抢加剧,runtime.mcall调用飙升 - 确认没加
GODEBUG=madvdontneed=1:它会让 Go 在释放内存时触发大量系统调用,间接拉高 sy%(内核态 CPU) - 检查
kratos run是否用了--debug:该模式会开启 full pprof、trace、以及所有 debug 日志,生产环境必须关闭 - Go 版本低于 1.21 时,若服务大量用
time.Now(),留意runtime.nanotime1占比异常——升级到 1.21+ 可显著降低该开销
真正难排查的,往往不是某段明显错误的代码,而是多个“合理”操作叠加后的副作用:比如一个中间件做轻量日志 + 另一个做指标打点 + 第三个做链路透传,每个都只消耗 0.3ms,但乘以 5000 QPS 就是 1.5 核持续占用。Kratos 的模块化设计让这种隐性叠加特别隐蔽,所以一定要从线程级开始往下挖,而不是在 handler 函数里反复加 log。



















