记一次go项目内存泄露问题排查

360 阅读3分钟

背景

日常维护的ADX项目,8G内存。某天内存使用率达到了 80%,触发了预警,考虑到平时内存使用也就是 20% 左右,因此八九不离十是发生了内存泄露。

排查思路

1、最近是否有影响系统稳定性的业务代码上线?

因为项目平时是自己在负责,跟踪了一下最近的上线的 checklist,发现并没有影响系统代码的需求上线。

2、项目所采用的中间件是否不稳定?

因为公司使用的codis集群,最近存在不稳定状态,老是重启,因此将codis做为切入点去分析。

3、有大体思路了,那就抓火焰图吧

因为项目接入了 pyroscope,因此登录目标机器手动开启后,抓到的内存使用空间的pprof如下图:

image.png 从上图可以看到 github.com/go-zookeeper/zk.Connect 及 github.com/go-zookeepe…(Conn).recvLoop 两个函数占比较大。结合对应代码

image.png 代码第342行 image.png 代码第793行 image.png 其中从代码分析有一个超时时间,ADX之前设置的为 200s,这样 

recvTimeout = 200*2/3 = 133
pingInterval = 133 / 2 = 66

这样当zk的proxy挂掉之后,需要 最少 66s(ping时间相对也太久了吧)最大 200s 的时间才能踢掉对应的 client,而ADX在使用此处codis时为 高并发,因此极容易导致内存泄露。所以建议可以往小了调整。

4、差点成功的一次解决

基于自己对代码的理解,于是调整了超时时间为 30s。重新发布项目后,在经历 5h左右观察后,发现是有效果的【大意了,差点以为彻底解决了,于是放心的玩耍去了。其实这个地方取决于codis重启的频次】

image.png 不出意外,长时间运行后还是存在内存泄露问题。emmmm,那就接着分析内存的pprof吧。

image.png 通过查看source可以发现

image.png 好家伙指定是这两个地方的。那就先去git上搜索一下有无相同问题吧。翻阅了一遍没有,自己还提了个 issue,可惜没人及时答复。

5、最终解决方案

既然别人没出现过,那么一般就不是三方插件的问题(先怀疑自己呗),好吧那就先看看自己代码吧。只能从zk.Connet 的这处引用开始查看。终于发现了真正的问题,原代码在监听的zk子节点发生变化时,会重新把zk也重新初始化一遍(可以理解为偷懒了,之前因为codis稳定,未发生问题)。于是乎改代码呗

去掉不必要的事件监听 image.png

zk连接初始化时新增加判断

image.png 终于这盛世如我所愿,在zk子节点发生 146次改动后,内存仍然保持稳定

image.png 火焰图结果也达到了预期结果。

image.png

Yes,大功告成。周六日可以愉快的🚴🏻了。