2. pprof 入门:安装、集成与两种开启方式
性能分析是 Go 并发调优的起点,而 pprof 是 Go 官方提供的运行时性能分析工具。本章将带你完成 pprof 的安装、集成,并深入理解 runtime/pprof 与 net/http/pprof 的区别,掌握开启 pprof 的两种核心方式。
2.1 pprof 的安装与集成
pprof 工具链分为两部分:Go 标准库内置的采样能力 和 独立的可视化分析工具。标准库无需额外安装,但推荐安装 graphviz 以生成火焰图和调用图。
安装建议: 在 macOS 上使用
brew install graphviz,在 Ubuntu/Debian 上使用 sudo apt install graphviz,在 Windows 上使用 choco install graphviz。安装后可通过 dot -V 验证。
集成 pprof 只需在代码中导入对应包,无需第三方依赖。以下是两种集成方式的对比:
| 集成方式 | 导入路径 | 适用场景 | 采样控制 |
|---|---|---|---|
| runtime/pprof | "runtime/pprof" |
CLI 工具、一次性任务、批处理 | 手动 Start/Stop |
| net/http/pprof | "net/http/pprof" |
Web 服务、长期运行的后台进程 | 通过 HTTP 端点触发 |
2.2 runtime/pprof 与 net/http/pprof 的区别
这两个包都基于相同的底层采样机制,但使用方式和生命周期管理有本质区别。下面通过一个结构图来理解它们的定位:
核心区别总结:
- 生命周期管理:
runtime/pprof要求开发者显式控制采样开始和结束;net/http/pprof通过 HTTP 请求触发采样,服务端自动管理。 - 输出目标:
runtime/pprof输出到任意io.Writer(文件、网络连接等);net/http/pprof直接写入 HTTP 响应体。 - 多 profile 支持:
net/http/pprof自动注册所有标准 profile 端点(CPU、heap、goroutine、mutex、block、threadcreate),而runtime/pprof需要逐个调用。 - 安全性:
net/http/pprof默认暴露所有端点,生产环境需配合中间件鉴权;runtime/pprof由代码逻辑控制,更安全。
2.3 开启 pprof 的两种方式
根据应用类型选择合适的方式。下面分别给出两种方式的完整代码示例和关键步骤。
方式一:使用 runtime/pprof(适用于 CLI 工具)
package main
import (
"os"
"runtime/pprof"
"log"
)
func main() {
// 1. 创建 CPU profile 文件
cpuFile, err := os.Create("cpu.pprof")
if err != nil {
log.Fatal(err)
}
defer cpuFile.Close()
// 2. 启动 CPU 采样
if err := pprof.StartCPUProfile(cpuFile); err != nil {
log.Fatal(err)
}
defer pprof.StopCPUProfile()
// 3. 执行业务逻辑(自动被采样)
doHeavyWork()
// 4. 创建 heap profile 文件(可选)
heapFile, err := os.Create("heap.pprof")
if err != nil {
log.Fatal(err)
}
defer heapFile.Close()
// 5. 写入堆内存快照
if err := pprof.WriteHeapProfile(heapFile); err != nil {
log.Fatal(err)
}
}
func doHeavyWork() {
// 模拟耗时操作
sum := 0
for i := 0; i < 1e8; i++ {
sum += i
}
}
注意:
StartCPUProfile 和 StopCPUProfile 必须成对调用,且只能同时存在一个 CPU profile 采样。如果忘记调用 StopCPUProfile,后续的 profile 写入会阻塞。
方式二:使用 net/http/pprof(适用于 Web 服务)
package main
import (
"log"
"net/http"
_ "net/http/pprof" // 匿名导入,自动注册路由
"time"
)
func main() {
// 1. 注册业务路由
http.HandleFunc("/work", workHandler)
// 2. 启动 HTTP 服务(pprof 路由已自动注册)
// 访问 /debug/pprof/ 查看所有 profile 列表
// 访问 /debug/pprof/profile?seconds=30 采集 30 秒 CPU
// 访问 /debug/pprof/heap 获取堆内存快照
log.Println("Server started at :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func workHandler(w http.ResponseWriter, r *http.Request) {
// 模拟业务处理
time.Sleep(100 * time.Millisecond)
w.Write([]byte("done"))
}
匿名导入 _ "net/http/pprof" 后,pprof 会自动在默认的 http.DefaultServeMux 上注册以下端点:
| 端点路径 | profile 类型 | 说明 |
|---|---|---|
/debug/pprof/ |
索引页 | 列出所有可用的 profile 及当前活跃 goroutine 数量 |
/debug/pprof/profile |
CPU | 默认采样 30 秒,可通过 ?seconds=N 调整 |
/debug/pprof/heap |
堆内存 | 当前堆内存分配快照,包含所有存活对象 |
/debug/pprof/goroutine |
Goroutine | 所有 goroutine 的堆栈跟踪,含 runtime 内部 goroutine |
/debug/pprof/mutex |
互斥锁 | 记录等待锁的堆栈,需先调用 runtime.SetMutexProfileFraction |
/debug/pprof/block |
阻塞 | 记录 goroutine 阻塞事件,需先调用 runtime.SetBlockProfileRate |
/debug/pprof/threadcreate |
线程创建 | 记录导致新 OS 线程创建的堆栈 |
2.4 两种方式的决策流程
下面的流程图可以帮助你快速选择适合当前场景的 pprof 集成方式:
生产环境安全建议: 如果使用
net/http/pprof,建议在服务启动时通过环境变量控制是否开启 pprof,或者使用独立的监听端口(如 :6060)仅在内网暴露。示例:go run main.go -pprof-addr=:6060。
2.5 验证集成是否成功
无论使用哪种方式,都可以通过以下步骤验证 pprof 是否正常工作:
- 对于 runtime/pprof: 运行程序后检查是否生成了
cpu.pprof和heap.pprof文件,文件大小应大于 0 字节。 - 对于 net/http/pprof: 使用 curl 或浏览器访问
http://localhost:8080/debug/pprof/,应看到 profile 列表页面。 - 使用 go tool pprof 验证: 执行
go tool pprof -http=:8081 cpu.pprof或go tool pprof -http=:8081 http://localhost:8080/debug/pprof/profile?seconds=10,应能打开 Web UI。
常见问题排查:
- 如果
net/http/pprof返回 404,检查是否使用了自定义的http.ServeMux,需要手动注册 pprof 路由。 - 如果 CPU profile 文件为空,检查是否调用了
pprof.StartCPUProfile后没有执行足够的业务逻辑(采样时间太短)。 - 如果 heap profile 没有数据,检查程序是否在 profile 写入前已经释放了所有内存(GC 后堆可能为空)。