一次由Erlang垃圾回收导致的内存泄漏

最近有一个erlang的服务因为oom被kernel kill掉了, 由于从监控来说这个服务确实是内存不断增长。所以这个服务应该在哪里有内存泄漏才对。于是我又一次开始定位这个问题。

定位到底哪个进程占用了最多的内存

由于在另一个节点有一个没有crash,但是内存仍旧很高的节点,所以这个问题处理起来很简单。erlang提供了完整的运行时系统debug环境。这里我们用etop,根据memory排序,查看具体哪个线程占用了最多的内存。

erl -sname etopnode -hidden -s etop -output text -sort memory \ 
-lines 20 -tracing false -node 'node_name@host_name' -setcookie your_cookie

在有erlang的机器上执行这个命令,即可连接到具体的节点,通过etop得到所有线程信息,按内存排序,显示最多内存的20条线程。etop记录里的memory使用KB做单位。

在这次问题里,我定位到的内容包括

  1. 最大内存占用来自一个每月跑一次的统计线程。该线程为一个gen_server,用于生成一个信用分排行榜。这个线程占用超过50%内存。大概900M左右的内存。
  2. 第二大内存占用者是mysql线程。该线程占用超过25%内存。大概500M内存

进一步代码分析

已经定位到了75%以上内存占用了。肯定是先分析第一内存占用的线程的具体逻辑了。

  1. 启动之后,就定时触发一个检查请求
  2. 该请求检查当前时间是否为每月月初。
  3. 如果是,获取一个分布式锁。
  4. 如果分布式锁获取成功,一次性从sql数据库加载所有用户的信用记录,生成一个排行,写回数据库。
  5. 处理结束

这个代码很简单,而且很确定,没有任何数据被这个线程一直hold着,除了那个定时请求。该线程有如下特点。

  1. 一个月只有一两次真正运行,其它时间都是在触发定时器,然后回归循环
  2. 运行的时候,会将一个非常庞大的用户数据加载到内存。
  3. 即使是加载过大量用户的数据,处理结束之后也会释放这些数据

通过remote shell验证猜想

显然,这个线程不太可能有无用内存没有被释放的问题,所以我的第一直觉就是垃圾回收问题。通过erlang的remote shell,登陆到该进程,可以执行强制垃圾回收等工作。

erl -sname foo -setcookie your_cookie -remsh node_name@host_name 

然后在上调用garbage_collect(Pid)请求,可以强制要求某个线程进行垃圾回收。

果然,对该线程调用过之后,etop根据内存排序,就找不到这个线程了。

更进一步,将线上数据库拉下来之后,本地执行每月初加载数据的逻辑,确实出现了内存增长到900M之后再也没下去的情况,并且mysql线程的内存也是一次就涨起来。我分析是因为数据是从mysql线程加载进来了,两个线程的内存泄漏原因是类似的。

如何优化?深入理解erlang垃圾回收

Erlang Garbage Collection Details and Why It Matters

深入了解erlang垃圾回收机制

实际上,这个东西在网上早就有人说过的。包括外国有人的erlang垃圾回收机制讲解,以及国内友人翻译的中文文章。

根据文章,这个内存泄漏的线程,其内存回收过程大概如下:

  1. 运行sql加载数据前,内存占用较低,使用fullsweep全扫描清理垃圾。
  2. 由于加载大量用户数据进内存,所以垃圾回收算法切换到了分代回收。而每次加载用户数据,都是一大块数据,根据垃圾回收算法分代策略,大块数据通常被划分到老年代。于是在fullweep之前大概率不会被回收。
  3. erlang的一个线程从分代回收算法切换回fullsweep,需要经历很多次分代回收(默认65535,2的16次方,减1),而这个线程平时没啥操作,所以很难切换回全扫描。

清楚了问题的原因,问题就简单了,存在的改善措施包括

  1. 加载数据的时候,不要用一个长期运行的线程,而是使用一个独立的线程加载,然后直接清理掉该线程。这样可以保证数据被清理。
  2. 加载数据的时候,不要一次性加载全部数据,而是使用limit,每次载入一部分数据,不让内存突然涨起来就行了。
  3. 不加载数据,直接使用sql语句,生成新的记录

我们选择了第三条,使用这个方式,不用额外加载数据,减少了网络传输。唯一问题是mysql需要负责更加多的工作,但是由于sql语句没有很复杂,所以也不用太担心。测试之后,确实内存增长消失了(废话),内存泄漏也就解决了。


最后的最后,顺便安利下《垃圾回收算法手册:自动内存管理的艺术》这本书。如果不是这本书的细致分析,我不可能如此简单就分析出问题所在,也不可能那么快解决问题。

猜你喜欢

转载自blog.csdn.net/zerooffdate/article/details/80205085