原创 吴就业 163 0 2024-02-06
本文为博主原创文章,未经博主允许不得转载。
本文链接:https://www.wujiuye.com/article/c891c95ad16b4917b83a81e349b90c1a
作者:吴就业
链接:https://www.wujiuye.com/article/c891c95ad16b4917b83a81e349b90c1a
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。
有个go项目的容器近两天几乎每天都异常重启一次,且两个节点基本都是差不多的时间异常重启。看了监控指标,发现CPU平稳,而内存是缓慢涨上去后,进程被操作系统kill掉,导致pod重启。
从内存指标可以看出,不会是因为突然的请求量上升所导致,而是应该存在内存泄漏了。 另外,从网络带宽指标看,流量也确实波动并不大,趋于平稳。
从监控指标还可以看出,go的线程数是平稳趋势,可以排除goroutine导致的内存泄漏。
这里我们需要借助pprof工具查看内存泄漏问题。pprof(Profiling in Go)是Go语言内置的一个性能分析工具。该工具可用于在运行时进行应用程序性能分析和剖析,帮助我们找出go进程的性能瓶颈和资源利用问题。例如:
排查思路:由于容器已经重启过,当前go进程内存消耗还是正常值,不过从监控指标已经看出,内存会缓慢的涨上去,因此我们是先查看当前时间的内存使用情况,记录下来,待一个小时以后,再看一次,对比看哪里的内存是上涨的,然后再追踪内存是从哪里分配的,最后再看代码,看看哪里占用了内存没有释放。
我们已经给该go项目启用pprof:
import _ "net/http/pprof"
func init() {
go func() {
http.ListenAndServe("0.0.0.0:7005", nil)
}()
}
进入容器,执行命令“go tool pprof -inuse_space http://127.0.0.1:7005/debug/pprof/heap
”。
输入top命令,查看内存占用的前10。
目前看各项占用的内存都是在正常值范围,比较可疑的是“crypto/x509.parseCertificate”,还不急着分析泄漏问题。
不过这里还看出一个问题,就是我们执行操作系统的top命令,看到占用的内存(RES),也是监控指标显示的内存值,当前已经1.4g,而pprof的top命令显示的heap内存占用才626MB,相差很大。
类似Java的堆内存+堆外内存,以及堆内存实际使用与已占用操作系统的内存。go除了堆内存使用,还有栈、gc、go的一些底层数据结构等使用的内存也是计算在堆外的,而堆内存已申请和已使用也跟java类似, gc后会有空闲的堆内存,不会全部马上归还给操作系统。而pprof的top统计的是当前已使用,不包含空闲的堆内存,所以看到的差距很大。
进入容器里面执行curl http://127.0.0.1:7005/debug/pprof/heap?debug=1
命令,可以查看go的内存占用情况。
字段含义说明:
从图中可以看出,go进程总共为heap申请了1.2G的内存,当前已使用785MB,空闲461MB。top命令显示的进程占用1.4g,go进程heap占用1.2G,还差两百M,就是Stack(栈内存使用)、MSpan+MCache+BucjHashSys(go底层内部结构体使用)、GCSys(GC使用)、OtherSys(其它内存使用)。所以内存使用是对得上的。
大概一个小时后,重新执行pprof的top命令,输出的前10堆内存使用如下图。
(方便对比,上一次的)
其中gitlab.lizhi.fm/middleware/lz_common_romefs/fio.(*ByteBufPool).Get
占用64.32mb刚好是内存池的最大大小,这是我们自己实现的内存池,说明内存池没有泄漏。而bytes.makeSlice、crypto/x509.parseCertificate都往上涨了。
通过peek bytes.makeSlice,发现bytes.(*Buffer).Write占用64.33MB,bytes.(*Buffer).Grow占用110.48MB。
继续peek Grow,发现与tls有关,占用110.48MB。
继续peek Write,由于是模糊匹配,一共有三个结果,中间第二个才是我们需要的,发现也与tls有关,占用64.33mb内存。
代码中,与tls有关的地方就是发送https请求从s3下载文件,所以检查下载文件调用链路上是否存在可疑的内存泄漏,发现如下疑点。
统计了访问日记,发现确实经常出现响应403。
所以问题就清晰了,由于403是有body的,没有close响应的body导致的内存泄漏。
修改后指标恢复正常。
声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。
记录一次工作中实战的Java内存泄漏问题排查,Pod重启后无法查看现场,但通过gc日记可以确认存在内存泄露,并通过运行一段时间发现有个Java类的实例数量非常高。
因为go标准库实现tls握手性能比较差,在一台8核的机器,只能到达2000这个量级,所以当到达某个临界点的时候,握手占用CPU过高,反过头来影响正常的业务请求,导致了业务请求处理变得十分慢。
用go开发的一个文件上传中间件,由于依赖了ceph这个c库,早期通过pprof排查,怀疑内存泄露在c层,而项目依赖ceph,于是就怀疑是ceph的问题。但通过使用jemalloc排查后,并未发现ceph有什么异常。最后使用最笨的方法,定位到github.com/chai2010/webp库存在内存泄露bug。
订阅
订阅新文章发布通知吧,不错过精彩内容!
输入邮箱,提交后我们会给您发送一封邮件,您需点击邮件中的链接完成订阅设置。