CPU高或内存涨主因是Goroutine泄漏、锁争用、GC频繁或字符串/日志滥用;pprof需正确采样、选型并解读栈帧,如30秒以上采样、top10定位业务函数、list查具体行号、heap需两次强制GC对比、goroutine需debug=2全量查看、trace要缩放查STW和GC标记。

直接结论:CPU 高或内存涨,90% 不是代码写得“慢”,而是 Goroutine 泄漏、锁争用、GC 频繁或字符串/日志滥用;pprof 不是“开了就能看出问题”,关键在采样方式、profile 类型选择和看懂栈帧含义。
怎么快速抓到真实 CPU 热点(不是 runtime.futex 占比高就去改锁)
runtime.futex 或 runtime.mcall 在 top 里占比高,大概率不是你业务函数慢,而是 Goroutine 调度太密集——比如几千个 goroutine 卡在 channel receive 上,或反复抢同一把 sync.RWMutex 写锁。
真正该盯的是业务函数本身耗时,而不是运行时调度开销:
• 必须采样 ≥30 秒:go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30,时间太短容易错过间歇性热点
• 进入交互后先输 top10,看排第一的是否是你的 handler、codec 或循环逻辑;如果不是,再输 top -cum 查调用链顶端
• 用 list 函数名 定位到具体行号,注意区分:是函数内循环没退出?还是调了 json.Marshal 这类反射-heavy 操作?
• 如果 web 生成的火焰图里大量扁平分支都指向 time.Now() 或 log.Printf,别优化算法,先删日志或换 zap.Sugar() + 条件包裹
heap profile 看不出泄漏?因为你没对比 or 没强制 GC
访问 /debug/pprof/heap 默认返回的是当前堆存活对象(InuseSpace),但若程序刚启动、GC 还没触发,或对象刚分配还没被回收,InuseSpace 可能很低——这不等于没泄漏。
• 要确认是否泄漏,必须做两次采集并对比:
○ 先请求一次 /debug/pprof/heap?gc=1(强制 GC 后采),记下 InuseSpace
○ 过 30 秒再请求同地址,若值持续上涨且无对应业务释放动作,基本可定性为泄漏
• 更准的方式是看累计分配:go tool pprof -alloc_space http://localhost:6060/debug/pprof/heap,然后 top,如果 strings.Builder.Write 或 encoding/json.(*encodeState).marshal 排前三,说明高频拼接或序列化在不断 new 对象
• 注意:sync.Pool 缓存的对象不会出现在 heap profile 里,但若 Pool 的 New 函数本身创建大对象(如 bytes.Buffer 底层切片过大),仍会推高 AllocSpace
goroutine 数量暴涨却查不到泄漏点?debug=2 是开关
/debug/pprof/goroutine 默认只返回状态为 running 或明显阻塞(如 chan send)的 goroutine,大量“活着但卡住”的会被过滤掉。
• 必须加 ?debug=2:curl "http://localhost:6060/debug/pprof/goroutine?debug=2" > goroutines.log,才能看到完整栈和所有 goroutine 的当前 PC 地址
• 重点关注状态字段含以下关键词的 goroutine:
○ chan receive:channel 没人 close,或 sender 已退出但 receiver 还在等
○ select:for-select 循环里缺 default 分支,导致永远阻塞在某个 case
○ semacquire:sync.Mutex 或 sync.WaitGroup 被某处 hold 住没释放
• 如果数量随 QPS 线性增长,立刻检查 HTTP client 是否设了 Timeout、DB query 是否漏了 rows.Close()、WebSocket 是否忘了 conn.Close()
trace.out 里看不出 GC 影响?因为没关注 STW 和标记阶段
go tool trace trace.out 打开后,“View trace” 页面默认缩放级别太粗,GC 事件容易被忽略。
• 点击右上角 “Find” → 输入 STW,定位每次 Stop-The-World 时间点,看是否超过 1ms(Go 1.22+ 目标是 sub-millisecond)
• 展开下方 “Goroutines” 行,拖拽缩放至 10ms 级别,观察 GC 标记(GC mark assist 或 GC worker)是否频繁抢占你的业务 goroutine
• 若发现大量 goroutine 长时间处于 Runnable 状态(黄色条),但没执行(绿色条),说明 CPU 资源不足或调度器被 GC 压制;若长时间 IOWait(蓝色条),则问题在下游依赖,不是 Go 代码本身
• trace 无法替代 pprof,它只告诉你“这次请求为什么慢”,而 pprof 告诉你“哪个函数长期吃 CPU”——两者要配合看,不能只盯一个
最常被跳过的一步:pprof 采集前没确认应用是否真启用了 net/http/pprof;import 了但没调 http.ListenAndServe,或监听地址绑在 127.0.0.1 却从容器外 curl,结果连 404 都收不到——先 curl -v http://localhost:6060/debug/pprof/ 看能否返回 HTML 列表,再动手分析。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。


















