一个让我如痴如醉的python内存泄露问题

聊一个有意思的话题, 一个让我如痴如醉的内存泄露问题 (memory leak). . .

熟悉我的人,知道我是从devops再到纯开发的岗位。虽然我写过很多的所谓运维平台,什么自动装机,什么集群管理,什么DBA管理,什么监控系统等等… 虽然前后端都是我自己搞,但我很并不喜欢写前端的东西,当然我写的那些的前端跟真正的前端没法比,只能说硬着头皮拿着angularjs来撸, 写的很一般 。  后来到了Admaster之后,发现写纯后端是如此的美妙,尤其是用python这种渣渣语言来实现高性能的服务,可以说坚定了我往后端底层深入的决心.   

貌似只要做过运维开发及运维的朋友,天然的喜欢高性能的东西,我也不例外。  我对高性能的软件架构很是感兴趣,为此我主动去熟悉各种的网络模型的优缺点,看一些用epoll实现的网络框架案例,把tornado和gevent的核心调度代码来回翻弄。 

还是具体说说我是怎么写出内存泄露的代码…  场景不复杂,就是实现的时候写了不少令人可气的业务逻辑.  

该文章写的有些乱,欢迎来喷 ! 另外文章后续不断更新中,请到原文地址查看更新.    http://xiaorui.cc/?p=3315

根据业务,我简单list一下流程:

A .  根据平台动态的加载业务代码,然后从redis取任务id

B .  从mysql里面取出id的相关所有数据, 这不是一条SQL就可以搞定的,需要各种的组合套取数据。 

C .  调用各种nlp,segment,scroe等等api

D .  加载AC自动机匹配关键词,自定义的摘要计算,黑白名单过滤,计数统计等等

E .  把数据扔到Elasticsearch里面.
F .   xxx
G .  xxxx

H.   xxxx

屁逻辑太多…  省略….

A,B,C,xxx函数逻辑是相关联的, 对于python的各种内存回收,我以前自信是理解的,但这次的内存泄露的BUG出来以后,让我很是醉心.  .  . 

这次遇到一个很严重的内存泄露问题,让我发现服务端开发并不是你理论知识好就可以搞定的。 学习是需要一个循环渐进的一个过程,每天睡觉前翻看APUE,不仅容易早睡,而且可以学点底层的东西。

这次开发项目服务名姑且叫service_migrate吧,这代码本身是有些复杂,在测试环境中是可以通过的,但在线上居然出现严重的内存泄露,这真是让人难以接受,120G的内存,被我给吃的9/10了…  因为服务有别的服务在跑,内核在不得已的情况下,把我的Worker进程给OOM干掉了.

要解决python的内存泄露,要理解python是怎么判定内存回收,  python是靠引用计数为主,分代回收为次的方案进行垃圾回收的。  现在一说python写的服务端,貌似大多是web服务的场景。 这个情况只要不用那种装13的黑魔法,很难出现大量的内存泄露问题,另外涉及到大量数据的运算不会在web层同步的返回,这也避免了内存泄露. 

python的哪里操作是会引用减少 ? 当一个对象引用数为0时,就会被回收.

– 函数运行结束,所有局部变量都被销毁,对象的引用计数也就随之减少。例如 foo(x) 运行结束,x 被销毁.
– 当变量被赋值给另一个对象时,原对象的引用计数也会减少.
– 使用 del 删除一个变量也会导致对象引用减少。例如 del x.
– 对象从集合对象中移除。例如 lst.remove(x).
– 包含对象的集合对象被销毁。例如 del lst.
其实很多时候,大家能见到的内存泄露代码都是可见的, 很容易看出来, 比如循环引用,这个还是比较好识别的!  另外是第三方的C模块写的不严谨,也会造成内存泄露。 

为了查找内存泄露的问题,我首先使用火焰图查看问题,结果没看出所以然。后来使用python解决内存泄露的经典工具memory_profiler, objgraph ,结果也是突然.   另外objgraph 还是有力度的,在我们已经知道内存泄露的点之后,再使用objgraph反推回去是可以验证的BUG的。 


这个应用服务管理着所有对外的实时数据,有点心急了,所有无法细心的琢磨这个工具的使用方法。 话说objgraph的文档是在粗糙,吐出来的内存数据也不好懂。还是那句话着急解决问题,没法静下心研究这工具。 通过stackoverload看到了一些回复,大多数是老生常谈的python引用计数的回收原理。 这原理做py的都懂,但问题代码中是在没有明显的问题代码。   

初步针对Python 内存泄露的解决方法:

     避免worker进程都被oom后,没有可用的worker,我又了一套这对worker的调度模块。    但这不是根本问题,有人说是ac自动机的问题,我跟同事用pyobject c代码又搞一轮子,还是出现,用了不少的内存泄露探测也没明显看出什么,只能是解耦拆解模块,来大量的数据测试。  最后才确定了问题。

 避免python worker进程都被oom后,没有可用的worker。  我这边把所有的平台都加入了Master Worker调度管理 ( 以前开源过这样的python进程调度管理工具,有兴趣的朋友到 https://github.com/rfyiamcool/ProcessHandler )。 这样master一直不断的把内存占用严重的进程干掉,并重新启动一个新进程,而针对每个worker做了max_requests限制,也就是说worker自己也会退出来。
虽然曲线救国绕过了OOM的坑,但这不是根本问题.
接着开始回溯这泄露问题,开始怀疑是ac自动机的问题,那我就直接用python搞一轮子,还是出现内存泄露,又怀疑Mysqldb的问题,好 ! 我把python Mysqldb的代码简单过了一遍.  话说myqsldb的代码写的那么复杂,根本没法懂其细节。
后来又用了不少的内存泄露工具也没明显看出什么,只能是一步步的解耦,拆解逻辑,拆解模块,然后通过大量的数据测试来确定问题。
说了这么多废话也该对python内存泄露问题做个总结了断了:

那么,如果要我单纯的说 怎么 解决内存泄露问题?

 一步步的拆解逻辑测试… 

话说对于后端的服务,不要只顾着功能逻辑测试,一定要兼顾着稳定性及cpu、内存的占比测试。 

下面是当时出现内存泄露的截图.  

我们可以发现出现了两种现象,CPU跑的满,内存占用很厉害。 解决了memory leak后,情况一切好转。 


很醒目的 Out of memory: KILL process pid…

#xiaorui.cc

May 01 17:45:11 HK-Hbase kernel: [40996]   508 40996     2285       12  12       0             0 sh
May 01 17:45:11 HK-Hbase kernel: Out of memory: Kill process 39752 (backtrack_migra) score 88 or sacrifice child
May 01 17:45:11 HK-Hbase kernel: Killed process 39752, UID 508, (backtrack_migra) total-vm:11863168kB, anon-rss:11618328kB, file-rss:4kB
May 01 17:46:01 HK-Hbase kernel: backtrack_migra invoked oom-killer: gfp_mask=0x280da, order=0, oom_adj=0, oom_score_adj=0
May 01 17:46:01 HK-Hbase kernel: backtrack_migra cpuset=/ mems_allowed=0-1
May 01 17:46:01 HK-Hbase kernel: Pid: 39759, comm: backtrack_migra Not tainted 2.6.32-504.16.2.el6.x86_64 #1
May 01 17:46:01 HK-Hbase kernel: Call Trace:
May 01 17:46:01 HK-Hbase kernel: [<ffffffff810d41b1>] ? cpuset_print_task_mems_allowed+0x91/0xb0
May 01 17:46:01 HK-Hbase kernel: [<ffffffff81127410>] ? dump_header+0x90/0x1b0
May 01 17:46:01 HK-Hbase kernel: [<ffffffff8122ee4c>] ? security_real_capable_noaudit+0x3c/0x70
May 01 17:46:01 HK-Hbase kernel: [<ffffffff81127892>] ? oom_kill_process+0x82/0x2a0
May 01 17:46:01 HK-Hbase kernel: [<ffffffff811277d1>] ? select_bad_process+0xe1/0x120

/var/log/messages

#xiaorui.cc
pr 01 17:46:01 HK-Hbase kernel: [ 5887]     0  5887    64063    23528   3       0             0 worker: extract
May 01 17:46:01 HK-Hbase kernel: [ 6809]   508  6809  1295200  1217202   2       0             0 backtrack_migra
May 01 17:46:01 HK-Hbase kernel: [10512]     0 10512    60903    21412   1       0             0 worker: extract
May 01 17:46:01 HK-Hbase kernel: [10620]     0 10620    61001    21463   5       0             0 worker: extract
May 01 17:46:01 HK-Hbase kernel: [11669]   508 11669   268832   214311  17       0             0 backtrack_migra
May 01 17:46:01 HK-Hbase kernel: [13230]   508 13230   374508   303953   5       0             0 backtrack_migra
May 01 17:46:01 HK-Hbase kernel: [16926]   508 16926  0188551  0129408  12       0             0 backtrack_migra
May 01 17:46:01 HK-Hbase kernel: [19710]     0 01710    63847    23266  23       0             0 worker: extract
May 01 17:46:01 HK-Hbase kernel: [20593]   508 20593  1287613  1233080  20       0             0 backtrack_migra
May 01 17:46:01 HK-Hbase kernel: [22162]   508 22162   347829   280500  23       0             0 backtrack_migra
May 01 17:46:01 HK-Hbase kernel: [24890]   508 24890   281240   210594  11       0             0 backtrack_migra
May 01 17:46:01 HK-Hbase kernel: [28477]   508 28477  1123502  1047891  16       0             0 backtrack_migra
May 01 17:46:01 HK-Hbase kernel: [30886]     0 30886     1072       22   7       0             0 sleep
May 01 17:46:01 HK-Hbase kernel: [35346]   508 35346   265765   211342   3       0             0 backtrack_migra
May 01 17:46:01 HK-Hbase kernel: [36858]   508 36858   971333   916659  22       0             0 backtrack_migra
May 01 17:46:01 HK-Hbase kernel: [37631]   508 37631   282119   211463   1       0             0 backtrack_migra
May 01 17:46:01 HK-Hbase kernel: [38789]   508 38789   184960   130535   1       0             0 backtrack_migra
May 01 17:46:01 HK-Hbase kernel: [41008]   508 41008    57681     3256  21       0             0 backtrack_migra
May 01 17:46:01 HK-Hbase kernel: [41521]     0 41521     1072       23  14       0             0 sleep
May 01 17:46:01 HK-Hbase kernel: [44142]     0 44142     1072       24   4       0             0 sleep
May 01 17:46:01 HK-Hbase kernel: [44143]     0 44143     1072       23   1       0             0 sleep
May 01 17:46:01 HK-Hbase kernel: [44144]     0 44144     1072       23   6       0             0 sleep
May 01 17:46:01 HK-Hbase kernel: [44149]     0 44149     1073       24   6       0             0 sleep
May 01 17:46:01 HK-Hbase kernel: [44194]   508 44194    57681     3250  23       0             0 backtrack_migra
May 01 17:46:01 HK-Hbase kernel: Out of memory: Kill process 39749 (backtrack_migra) score 77 or sacrifice child

推荐大家多研究下 objgraph模块,  可以打印对象的信息及引用,还可以打印出函数调用关系,像这样:



END。


大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc

3 Responses

  1. 无感 2016年11月21日 / 上午10:08

    说了这么多,我并没有收获

    • 峰云就她了 2016年11月21日 / 下午2:27

      确实说了不少废话,虽然前期使用了不少检测工具. 因为内存增长快,所以用拆逻辑的方法很容易发现问题.

  2. 厉害 2016年8月10日 / 上午9:32

    这个过程太痛苦了

发表评论

邮箱地址不会被公开。 必填项已用*标注