Go 服务排查:pprof 实战

Go 服务的排查工具链比 Java 简洁:标准库自带 pprof,挂上接口就能拿到 CPU、内存、协程、阻塞四类数据。但简洁不等于安全——net/http/pprof 默认没有任何鉴权,挂到公网入口等于把进程内部结构、调用参数甚至内存快照公开出去。

接入:独立端口

import (
    "net/http"
    _ "net/http/pprof"
)

go func() {
    // 独立端口,只监听内网地址
    http.ListenAndServe("127.0.0.1:6060", nil)
}()
做法是否推荐原因
业务端口上挂 /debug/pprof不推荐与业务同入口,最容易暴露到公网
独立端口 + 内网地址推荐不经过网关,不对外暴露
独立端口 + 鉴权中间件推荐需要跨机器抓取时使用

抓取本身也有代价:profile 会让服务做 30 秒 CPU 采样,heap 会做内存统计,?seconds= 被设成很大的值时采样开销会持续存在。因此这个端口必须只在内网可达。

五类 profile 的用途

profile回答的问题采样方式开销
profile(CPU)CPU 时间花在哪些函数默认 30 秒采样中,建议 30 秒以内
heap内存被谁占用、谁在分配采样,默认 512KB 一个样本
goroutine协程有多少、都卡在哪全量栈快照低,抓栈有极短停顿
block谁在阻塞等待(channel、锁、IO)需先设置阻塞采样率
mutex锁竞争有多严重需先设置竞争采样率高,生产慎用

blockmutex 默认关闭,因为采样会影响性能:

runtime.SetBlockProfileRate(1000)     // 每 1000 次阻塞采样一次,生产建议 1000 以上
runtime.SetMutexProfileFraction(100)  // 每 100 次锁竞争采样一次,窗口期才开

取舍观点:block 可以在生产以很低采样率长期开启;mutex 只在排查锁竞争的窗口期开几分钟,采完立即设回 0。

常用命令与判读

# CPU 热点:采样 30 秒
go tool pprof http://10.0.0.12:6060/debug/pprof/profile?seconds=30

# 内存:默认看当前存活对象的 inuse_space
go tool pprof http://10.0.0.12:6060/debug/pprof/heap
go tool pprof -alloc_space http://10.0.0.12:6060/debug/pprof/heap    # 看累计分配,找分配热点

# 协程
go tool pprof http://10.0.0.12:6060/debug/pprof/goroutine

# 阻塞与锁竞争
go tool pprof http://10.0.0.12:6060/debug/pprof/block
go tool pprof http://10.0.0.12:6060/debug/pprof/mutex

go tool pprof -http=:6060 http://10.0.0.12:6060/debug/pprof/profile?seconds=30

进入交互界面后:

命令作用典型用法
top按开销排序的函数列表top20 看前 20 名
top -cum按累计时间排序找到调用链上游而不是叶子函数
list <函数名>逐行显示耗时分布list OrderService.Get 定位到具体行
web生成调用图,需要 graphviz看调用关系与宽度

判读要点:

观测值(top 输出)判断
flat 高且 cum 接近 flat该函数自身耗 CPU:计算、序列化、加解密
cum 高但 flat只是调用链路过,热点在子调用里
热点在 runtime.mallocgc / runtime.gcDrain分配过多导致 GC 吃 CPU,去优化对象分配
热点在 encoding/json序列化开销大,考虑换编解码库或减少字段
heap 的 inuse_space 集中在某业务结构内存泄漏或缓存无上限

GODEBUG=gctrace=1:读 GC 输出

GODEBUG=gctrace=1 ./app

输出形如:

gc 12 @3.402s 1%: 0.021+2.1+0.014 ms clock, 0.17+0.42/1.9/0.031+0.11 ms cpu, 32->34->17 MB, 40 MB goal, 8 P
字段含义判读
gc 12 @3.402s第 12 次 GC,进程启动后 3.4 秒频率=次数/时间,超过每秒 1 次就偏高
1%GC 占用的 CPU 比例超过 10% 说明 GC 是主要开销
0.021+2.1+0.014 ms clockSTW 扫描 + 并发标记 + STW 收尾两端之和是真实停顿,中间段是并发不阻塞
32->34->17 MBGC 前堆、GC 后堆、存活堆存活堆接近 GC 前堆=对象大量存活,疑似泄漏
40 MB goal下次 GC 的目标堆大小目标长期贴着小值说明 GOGC 太激进
8 P使用的 P 数量与 CPU 配额对照,容器里常被限制
GODEBUG=gctrace=1 ./app

生产注意事项

事项建议
CPU 采样控制在 30 秒内,避免多个请求同时打
内存快照heap?debug=1 只给统计,debug=2 给完整栈且文件大,先看 debug=1
mutex profile只在窗口期开,采完立即设回 0
端口暴露仅内网、独立端口、加鉴权或 IP 白名单
抓取留证采样文件归档到故障目录,便于复现与前后对比

排查流程

1. curl 'http://127.0.0.1:6060/debug/pprof/goroutine?debug=1'   先看协程数与栈聚合
2. go tool pprof -http=:6060 'http://127.0.0.1:6060/debug/pprof/profile?seconds=30'
3. top -cum 找调用链上游 → list <函数> 定位到行
4. GODEBUG=gctrace=1 或 runtime/metrics 确认 GC 开销
5. 修复后压测对比,同样位置再抓一次 profile

巡检项

  • /debug/pprof 是否只在内网可达,用一次外网探测验证。
  • goroutine 数与堆内存进监控,设单调上涨告警。
  • CPU 使用率与 GC CPU 占比分开监控,用于区分业务吃 CPU 与 GC 吃 CPU。
  • Go 版本、GOGCGOMAXPROCS 纳入配置管理,容器内必须显式设置 GOMAXPROCS

小结:pprof 五类 profile 各管一件事——CPU 找热点、heap 找内存、goroutine 找泄漏、block 与 mutex 找等待;用 top -cumlist 从调用链落到具体代码行,配合 GODEBUG=gctrace=1 区分业务 CPU 与 GC CPU,同时把端口限制在内网并控制采样开销。

笔记加载中…