压测时生成可用火焰图的关键是采样时机、符号完整性与数据格式三者对齐:需独立debug端口隔离pprof、压测与采样时间严格覆盖、禁用-strip、同步二进制、使用原版flamegraph.pl,并交叉验证go tool trace。

压测时生成可用的火焰图,关键不是“能不能”,而是采样时机、符号完整性和数据格式三者必须同时对齐——缺一不可。
压测前必须起独立 debug 端口
别把 /debug/pprof 注册在主业务端口上。高并发压测时,/debug/pprof/profile 是阻塞式 endpoint,一旦被压测流量打中,会卡住整个 pprof handler,导致采样失败或超时返回空 profile。
- 单独监听一个 debug 端口,例如
http.ListenAndServe("127.0.0.1:6061", nil) - 导入
_ "net/http/pprof"后,只在这个端口暴露 pprof,和业务完全隔离 - 压测命令(如
ab)目标地址必须是业务端口(如:8080),而采样请求必须指向 debug 端口(如:6061)
ab 压测期间如何触发准确采样
直接在 ab 开始后立刻发 curl 采样,大概率拿到空图——因为 ab 启动到真实流量打满有延迟,而 /profile?seconds=30 是服务端计时,它不管有没有请求进来。
- 先用
ab -n 1 -c 1 http://localhost:8080/快速探活,确认服务就绪 - 再开一个终端,执行:
curl -s "http://localhost:6061/debug/pprof/profile?seconds=60" -o cpu.pprof—— 这个 60 秒要覆盖完整压测周期 - 紧接着在另一个终端跑真实压测:
ab -n 10000 -c 200 http://localhost:8080/api/xxx - 确保压测时长 ≤ 采样时长,且中间无中断
生成火焰图时最常踩的三个坑
即使采样成功,go tool pprof 仍可能输出空白、扁平或全是 runtime.mcall 的 SVG——这不是工具坏了,是构建或运行环节断了链。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
-
go build时用了-ldflags="-s -w":必须去掉,否则函数名全丢,改用go build -ldflags="" main.go - 本地分析远程压测产生的
cpu.pprof:二进制没同步过去,pprof找不到符号表,结果全是???;正确做法是把压测机上的二进制也拷过来,或用pprof -symbolize=exec - 误用 npm 安装的
flamegraph:Brendan Gregg 原版flamegraph.pl才兼容 Go 的堆栈格式,npm 包常因 Perl 版本或解析逻辑错乱导致 SVG 渲染失败
验证火焰图是否可信的唯一方式
别只盯着「最宽的那条」——很多情况下,runtime.futex 或 runtime.usleep 占宽,并非锁竞争,而是 GC STW 或 goroutine 频繁调度。真正要交叉验证的是 go tool trace 中的 “Goroutines” 和 “Scheduler” 视图。
- 用同一份压测数据生成 trace:
curl "http://localhost:6061/debug/pprof/trace?seconds=20" -o trace.out - 运行
go tool trace trace.out,看是否存在大量 goroutine 处于 “Runnable” 但迟迟不执行,或 “GC” 栏出现密集红块 - 如果火焰图热点和 trace 中的调度瓶颈/STW 时间段重合,才说明优化方向是对的
火焰图本身不撒谎,但它只回答“哪里耗 CPU”,不解释“为什么耗”——这个“为什么”,得靠 trace、日志、代码上下文一起拼出来。漏掉任何一环,都可能把 GC 调优做成算法重写。

















