文章目录
背景
我们使用Go语言开发了一个后台服务,在发布测试环境后,发现内存使用量会随着时间的推移持续增加。因此服务的Pod会隔一段时间重启一次,因此,需要排查一下该问题。此文是对排查过程的记录以及排查后的思考总结。
环境准备
本文假设开发机环境中已经安装了go、pprof、graphviz,并且后台服务中已经集成了pprof。
业务中内存泄漏的现象以及排查思路
内存泄漏的现象
我们将服务发布到测试环境中之后,可以从内存监控的看板中看到,内存使用量随着时间的推移会一直增加,而且会一直达到内存设置的限制并且重启Pod。这种情况的出现,就是内存出现了泄漏的问题。
排查思路
使用Go语言开发的后台服务,在遇到这种情况时,我们首先应该想到的是可能Goroutine出现了泄漏,也就是说,可能开启了大量Goroutine,但是没有进行回收导致。因为Go语言程序的基本运行单位就是Goroutine,因此大多数内存泄漏都是Goroutine的泄漏。我们按照重点来排查,可以节约时间和精力。
我先在开发机上运行起来服务,然后请求pprof来查看Goroutine的运行情况:
请求http://10.111.55.111:8081/debug/pprof/
,(10.111.55.111
为我的开发机IP)可以看到:
然后我们选择查看其中的Goroutine:
再过一段时间后,我们再次刷新一下,再次查看Goroutine的数量:
可以发现蓝框标记的Goroutine数量一直随着时间的推移而增加,这就是内存泄漏的Goroutine。如果发现变化比较缓慢,我们也可以进行压力测试后再观察。
按照展示的调用信息,我们定位到Redis线程池实现中的这行代码。可以看到,是这行代码发生阻塞,这行代码是做什么的呢?其实就是Redis线程池的定时回收空闲线程功能,只是我们有大量的空闲线程还没有到时间被回收。于是阻塞在了这里。
接着往上找调用函数,就可以发现是新建Redis连接池时调用的该函数。
最终可以定位到导致内存泄漏的原因:其实就是我们在很多地方新建了Redis连接池,但是设置的关闭空闲连接的时间又不合理,导致在大量请求过来时,就会不断的累计连接数量,