版权声明:本文为博主原创文章,转载请注明出处:https://twocups.cn/index.php/2022/04/07/47/
结论先行
使用pprof进行golang程序的内存异常排查。
# 查看程序总体情况
go tool pprof http://127.0.0.1:6060/debug/pprof/profile
# 累计内存的使用情况
go tool pprof -alloc_space http://127.0.0.1:6060/debug/pprof/heap
# 当前内存的使用情况
go tool pprof -inuse_space http://127.0.0.1:6060/debug/pprof/heap
# 交互指令
top # 占用内存最多的函数
top -cum # 累计占用内存最多的函数
list [函数名] # 该函数下每行代码的内存占用情况
trace # 栈调用
# 记录当前pprof情况,并生成文件
curl http://localhost:6060/debug/pprof/heap?seconds=60 > heap.out
# 通过heap.out文件离线进入pprof web页面
go tool pprof -http=:7890 heap.out
# 通过heap.out文件离线进入交互命令行
go tool pprof heap.out
# 显示每行代码的资源消耗
list [函数名]
问题
今天给metricbeat做压测的时候,先写了程序把数据库的会话消息塞满,然后观察metricbeat是否能正常采集数据并且上报。当我将会话消息提升到4.1w~4.2w时,metricbeat出现了日志断流。排查后发现是因为metricbeat在第一次数据库会话消息拉满的时候退出了,因为是非正常退出,也没有异常报错,所以当时花了些时间才发现metricbeat的退出。
排查后发现是内存爆了。metricbeat在启动时通过Cgroup做了最大为5GB的内存限制,而在会话消息拉满后metricbeat内存不够用导致自己退出。暂时解除了Cgroup的内存限制后,发现metricbeat的内存占用最高时到达15GB,并且当会话消息流量缩小后,这个内存占用也没有变化。
我怀疑是发生了内存泄漏,所以问题就变成排查metricbeat中的内存泄漏问题。
排查
pprof
由于metricbeat是golang写的,所以自然就用到了pprof去排查,要去搞明白是哪些代码导致了巨大的内存消耗。
首先,需要在metricbeat的代码中加入pprof,并启动端口监听方便我们访问。
import _ "net/http/pprof"
go func() {
http.ListenAndServe("0.0.0.0:6060", nil)
}()
此时,可以查看下程序的总体情况。
go tool pprof http://127.0.0.1:6060/debug/pprof/profile
可以通过这行指令简单看下统计项,包括goroutinue数量、heap数量、mutex数量、block数量。
我查找的是内存异常,所以着重看下goroutinue数量和heap数量。goroutinue溢出的话也会导致内存溢出,一般是一个goroutinue占4KB。我当时是有2500+个goroutinue,虽然也很多,但不是造成内存异常的原因。
接下来,看下heap信息。heap信息只能查到代码里哪里使用内存多,并不能说使用内存多的地方就是有问题的。pprof查看heap信息有两个指令。
# 累计内存的使用情况
go tool pprof -alloc_space http://127.0.0.1:6060/debug/pprof/heap
# 当前内存的使用情况
go tool pprof -inuse_space http://127.0.0.1:6060/debug/pprof/heap
一般有条件的话,最好是查看当前内存的使用情况,更容易看出问题。通过这两条指令,我们可以进入一个和gdb类似的交互命令行。
# 交互指令
top # 占用内存最多的函数
top -cum # 累计占用内存最多的函数
list [函数名] # 该函数下每行代码的内存占用情况
trace # 栈调用
正常的排查过程是通过top查看占用内存的函数,然后通过list查看该函数中具体哪一行用了最多的内存。但list命令使用的前提是该机器上存在源码,所以我们可以通过以下指令把pprof状态记录下来。
curl http://localhost:6060/debug/pprof/heap?seconds=60 > heap.out
这条指令会记录60s内的pprof状态,并在当前文件夹下生成heap.out文件。
我把这个文件分别发送到了我的mac和开发机上,mac用于看pprof函数调用图和火焰图,而开发机用于看源码相关的具体内存占用。
Mac
mac主要用于显示可视化页面,我们通过以下命令在我们的mac上生成函数调用图。
go tool pprof -http=:7890 heap.out
此时,浏览器会默认打开localhost:7890端口,展示图片。
第一次打开会遇到图形化插件Graphviz未安装的问题。大家直接去Graphviz官网看下如何安装,一行代码的事情我就不细说了。
# Graphviz 官网
https://graphviz.org/download/
说回web页面,会展示程序的内存调用图。红色的框框代表申请内存,绿色的框框代表释放内存。框越大、连接线越粗代表设计的内存就越多。正常来说,一眼就能看到哪个函数申请的内存最多了。
由于我们上面采集的是heap信息,所以这里是内存信息的关系图。如果是之前采集的是cpu信息,那么这里的函数关系图显示的就是cpu使用时间。
# 分析cpu使用时间
curl http://localhost:6060/debug/pprof/profile?seconds=60 > heap.out
开发机
知道占用内存最多的函数,就可以来开发机上操作了,因为源码都在开发机上。
# 进入交互命令行
go tool pprof heap.out
# 显示每行代码的资源消耗
list [函数名]
这样我们就能直接看到内存占用最多的代码是哪几行了,然后针对这几行代码进行优化即可。