延迟上去了,结构化日志里的字段却都对得上。时间花在哪一段代码上,日志本身答不了。字段怎么打进结构化日志,可以看站内这篇 Go 标准库 slog。这里要接的是性能剖析,看 CPU、内存这些开销落在哪,两件事不要混成一篇。
net/http/pprof 包文档 写明:这个包通过 HTTP 服务提供运行时剖析数据,格式就是 pprof 可视化工具所期望的那种。常见用法是为了副作用而导入,让它把处理函数注册上:
import _ "net/http/pprof"
路径都以 /debug/pprof/ 开头。文档还写了:从 Go 1.22 起,这些路径都必须用 GET 访问。如果程序本身还没有在跑 HTTP 服务,需要自己起一个。文档给的例子绑在 localhost:6060,写法是在 main 里加上:
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
默认会用 DefaultServeMux。如果没用默认 mux,就要把处理函数注册到自己正在用的那个 mux 上。Diagnostics 文档的 Profiling 一节 也写了,这些处理函数可以挂到别的路径和端口。该页举过把 pprof.Profile 注册到 :7777 上某个自定义路径的例子。剖析数据可以在测试里用 go test 采集,也可以通过 net/http/pprof 提供的端点采集,再用 pprof 工具过滤和可视化。
官方预先提供的 profile 类型,Diagnostics 页从这些讲起:cpu 看程序在主动消耗 CPU 周期时把时间花在哪(不是在睡眠或等 I/O)。heap 报告内存分配样本,用来观察当前和历史上的内存占用,并检查有没有泄漏。另外还有 threadcreate、goroutine,以及默认不启用的 block、mutex。后两者分别要先调用 runtime.SetBlockProfileRate 和 runtime.SetMutexProfileFraction。包文档的用法示例也是从 heap 和一段 CPU 剖析开始的:
go tool pprof http://localhost:6060/debug/pprof/heap
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
第二条会采集 30 秒 CPU 剖析。Profile 这个处理函数对应的就是 CPU profile,文档写明持续时间由 seconds 这个 GET 参数决定,不传则默认 30 秒。同一参数用在 CPU profile 和 trace 上是采集时长。用在 allocs、block、goroutine、heap、mutex、threadcreate 这些 profile 上,文档写的是返回一份增量 profile。查询参数还可以用来改响应格式:debug=0 是二进制(默认),大于 0 则是纯文本。heap 上的 gc=N 在大于 0 时会先跑一轮垃圾回收再做剖析。想先看有哪些 profile 可用,文档让你打开 http://localhost:6060/debug/pprof/。默认情况下,runtime/pprof.Profile 里列出的那些 profile 都可以通过 Handler 拿到,另外还有这个包自己定义的 Cmdline、Profile、Symbol 和 Trace。执行跟踪数据是给 go tool trace 用的:文档的例子是先用 curl 把 /debug/pprof/trace?seconds=5 存成文件,再交给 go tool trace 打开。Trace 的持续时间同样由 seconds 决定,不传则默认 1 秒。
Go 博客介绍 pprof 的文章 后半也写了同一套 HTTP 接口:把 net/http/pprof 导入后,go tool pprof 只要给服务的剖析 URL,就会下载并查看一份实时 profile。博客前半演示的是独立程序里用 runtime/pprof 把 CPU 或内存 profile 写到文件,再交给 go tool pprof 看。对已经在跑的服务,走 HTTP 这一层更直接。Diagnostics 把可视化交给 go tool pprof,可以看文本列表、调用关系图,也可以用 weblist 在 HTML 里一行行对照源码耗时。上游 pprof 还支持火焰图,用来沿着某一条祖先路径放大或缩小。生产环境做剖析是可以的,但启用某些 profile(例如 CPU profile)会带来开销,建议先估开销再打开。多项剖析可能互相干扰,更稳妥的是一次只采一种。若服务有很多副本,该页的建议是周期性、随机挑一个进程来采集,把结果留下来再分析。它解决的是把运行时开销定位到代码路径上,并不是搭一整套监控或追踪平台。
你们现在是想先看 CPU 把时间花在哪,还是内存一直在涨、准备先拉一份 heap?