Caddy性能剖析
**程序剖析(profile)**是程序运行时资源使用情况的快照。它对定位问题区域、排查bug/崩溃、优化代码非常有帮助。
Caddy使用Go自带的剖析工具pprof采集profile。
profile可以反映CPU与内存消耗、goroutine栈信息,也有助于定位死锁或高竞争同步点。
当你向Caddy报告某些问题时,我们可能会要求提供profile。本文说明如何在Caddy中采集profile,以及如何大致阅读pprof输出。
开始前先知道两点:
- **Caddy profile不是安全敏感信息。**它包含技术统计信息,不包含内存内容,不会赋予系统访问权限,可安全分享。
- **profile开销较轻,可在生产环境采集。**很多用户都这么做,这是推荐实践。
获取profile
profile可通过管理接口的/debug/pprof/访问。在运行Caddy的机器上打开:
http://localhost:2019/debug/pprof/
你会看到一个简单列表,例如allocs、goroutine、heap、profile等。
这些计数可用于快速识别泄漏:持续刷新页面,如果某个计数一直上涨,就可能有泄漏。例如heap增长可能是内存泄漏,goroutine增长可能是协程泄漏。
常用的profile是:
- goroutine:函数栈
- heap:内存
- profile:CPU
其余profile可用于分析锁竞争、阻塞、死锁等问题。
下载profile
pprof索引页里直接点击链接通常是文本格式(便于快速阅读);但二进制才是默认格式。索引页多数链接会附带?debug=把它变成文本,CPU profile(profile)没有文本表示。
常见查询参数(见Go文档):
debug=N(除CPU外):N=0返回二进制(默认);N>0返回纯文本gc=N(heap):N>0表示采样前先执行一次GCseconds=N(allocs/block/goroutine/heap/mutex/threadcreate):返回差分profileseconds=N(cpu/trace):采样持续N秒
这些都是HTTP端点,所以你也可以用curl/wget下载。
下载后可上传到GitHub issue评论,或用pprof.me在线查看。CPU profile也可用flamegraph.com。
远程访问
如果你已能本地访问管理API,可跳过本节。
默认管理API只监听回环地址。你可用以下方式远程访问/debug/pprof:
通过站点反向代理
reverse_proxy /debug/pprof/* localhost:2019 { header_up Host {upstream_hostport} }
这会把profile暴露给可访问站点的人。若不希望公开,请增加认证模块。
(注意必须加/debug/pprof/*匹配器,否则会代理整个管理API。)
SSH隧道
在本机执行:
ssh -N username@example.com -L 8123:localhost:2019
它会把你本地localhost:8123转发到远端example.com上的localhost:2019。
然后在另一个终端请求:
curl -v http://localhost:8123/debug/pprof/ -H "Host: localhost:2019"
如两端都使用2019端口(且本地未占用),可省略Host头。
如果想后台长期运行隧道:
ssh -f -N -M -S /tmp/caddy-tunnel.sock username@example.com -L 8123:localhost:2019
关闭隧道:
ssh -S /tmp/caddy-tunnel.sock -O exit e
远程管理API
你也可以把管理API配置为允许授权客户端远程访问。(待补充专门文章。)
goroutine profile
goroutine dump用于查看当前有哪些goroutine及其调用栈,即哪些代码正在执行或阻塞等待。
访问/debug/pprof/goroutine?debug=1会看到按调用栈聚合的输出。首行例如:
goroutine profile: total 88
含义是当前总共有88个goroutine。
后续goroutine条目格式大致为:
<count> @ <addresses...>
<count>:拥有相同调用栈的goroutine数量@后地址:函数调用地址(调用帧)
以#开头的行是给读者看的注释,显示当前栈轨迹,格式为:
<address> <package/func>+<offset> <filename>:<line>
完整goroutine dump
使用?debug=2可获取完整dump:每个goroutine都展开,不合并同栈条目,输出会很大。
完整dump中每个goroutine首行还会提供:
- goroutine编号
- 状态(如
running、IO wait、select、chan receive、semacquire、syscall等) - 存活时长
这些信息对于排查goroutine泄漏、连接堆积等问题很有价值。
内存profile
内存(heap)profile追踪堆分配,是系统内存消耗的核心来源之一。频繁分配也是常见性能问题来源。
heap输出与goroutine输出类似,首行格式一般是:
<live objects> <live memory> [<allocations>: <allocation memory>] @ <addresses...>
通过调用栈你通常可以判断是哪条热点路径产生了大量分配,以及是否有对象池在生效。
CPU profile
CPU profile用于分析Go程序把CPU时间花在了哪里。
CPU profile没有纯文本格式。下载方式:
/debug/pprof/profile?seconds=N
采样N秒后会得到二进制文件(常见文件名为profile)。采样期间可能有轻微性能影响(其他profile影响通常很小)。
go tool pprof
使用Go内置分析器读取profile(不仅限CPU):
go tool pprof profile
进入交互模式后,常用命令:
top:查看最耗资源的函数web:浏览器打开调用图svg:生成调用图SVGtree:表格形式查看调用树
例如:
(pprof) top
你可进一步过滤,例如忽略runtime查看业务代码热点:
(pprof) top -runtime
可视化
执行svg或web可得到图形化视图:

如何阅读图请参考pprof文档。
对比profile(Diff)
改完代码后,可用前后profile做差分分析:
go tool pprof -diff_base=before.prof after.prof
在交互里再执行top可看到增减最明显的函数。
差分同样可视化:

这有助于直观看到改动对性能的影响。