使用go性能分析工具pprof分析内存泄漏问题

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

前言: 最近公司有一个项目占用内存非常高,启动以后就占用8到10个G,而且随着程序运行,内存会越来越大,一两天就能到15G,达到服务器设置的上线挂掉,但是重启一下又能继续使用,老大让我定位优化一下,哈哈,我说内存不够,机器来凑,实在不行,晚上重启,这不就解决了嘛。手动狗头!

熟话说:golang10次内存泄漏,8次goroutine泄漏,1次真正内存泄漏,还有一次是cgo

熟话是谁?不知道

言归正转:布置下来的任务还是需要落实的,初步分析是内存泄漏问题,可能是有些地方使用缓存或者对象一直没有释放。那接下来就是去定位啦,以下只提供解决的思路。

测试为本地调试和测试环境

相关指令:

# 浏览器查看
http://localhost:7777/debug/pprof

# 查看内存占用
go tool pprof --text http://localhost:7777/debug/pprof/heap
# 打开web分析
go tool pprof -http=:7778 http://localhost:7777/debug/pprof/heap
复制代码

1.打开go的pprof工具。只需在项目里引入:****_ "net/http/pprof"即可

_ "net/http/pprof"
复制代码

2.启动程序,浏览器访问

http://localhost:7777/debug/pprof/heap

看样子堆内存信息比较多,点开看看

咋看不清了,万恶的马赛克,哈哈 我自己打的,保密原因哈,不是故意不给家人们看的,大概意思是在程序的这个位置创建的对象占用了很大的内存,好大,打开项目看看是啥。

果然,代码那里读取了一个模型文件,占用就有一个多G。这么简单?问题就解决了?

拉到网页最下面查看:

感觉不对,上面显示的alloc:对分配空间字节数也就1.2G刚好和这个模型差不多,系统总内存也才2.1G。但是我运行环境是同top指令查看命名占用了5个G啊,还有3个多G被谁吃掉了?

不管了,先注释掉模型加载代码试试看。注释,重启,内存果然降了1个多G。但是还是被吃掉了3个多G。

3.重新回滚项目,使用pprof web UI直观的看下对象大小和占用情况

指令:

go tool pprof -http=:7778 http://localhost:7777/debug/pprof/heap
复制代码

查看直观的对象内存分配图,也是只占用一个G

4.再回到原点,想了一下go内存泄漏

golang10次内存泄漏,8次goroutine泄漏,1次真正内存泄漏,还有一次是cgo

因为项目很少使用goroutine,所以一开始就根据代码排除掉了这个原因,那内存有没有被记录到,难道是因为cgo?

5.一条路走到黑

查看一下项目里面是否有cgo相关的插件引入

找啊找啊找啊,好像真找到了

项目引入了一个结巴分词,结巴分词工具使用完以后需要释放掉

好,既然这样,先注释到这个结巴分词加载试试,注释重启,哎,哎,哎!好像真是这个问题。内存终于降下来了,苍天不负有心人啊。但是这个玩意什么时候free呢,按道理是使用完成以后free就行,但是为了项目的访问速度,每个活跃商户都会将它加载到内存中,加上商户自定义的关键词分词。也就是说商户如果一直活跃的话就一直在内存中,而且会越来越大,这也是为什么项目内存会递增的原因呢。好吧,找到问题了,但是又好像没有解决问题。

优化:想在项目分为4个节点,也就是每个内存节点都会加载一次活跃的用户,并且加载这个结巴分词对象。那我把它改成分布式的加载问题内存不久节约了四分之三吗?原来是每个10个G,如果分布式加载的话每个节点就只需要加载2.5个G。

6.解决方法

使用python根据用户id做个hash路由,每个请求先请求python项目接口,python根据节点通过dns自动发现存活节点,然后使用用户id做hash转发。完美解决!

python使用的是flask框架,线上直接将接口原接口切换成python的项目的接口,完美融合,内存终于降下来了啊!

7总结:

golang10次内存泄漏,8次goroutine泄漏,1次真正内存泄漏,还有一次是cgo

pprof只对纯go分析有用,cgo的问题pprof是无法定位的,只能通过对代码的熟悉或调试去定位,或者是用BCC工具去跟踪操作系统内核去分析解决。我自己的解决方式偏向于删除我怀疑的部分代码然后重启来比较内存变化,这样更直观,只是有时候方向不对会花费比较多的时间。

猜你喜欢

转载自juejin.im/post/7083412727498014734