一次python内存溢出的排查

最近在我的项目中,出现了内存持续增长的情况。这也是我第一次碰到这种情况,以前在写c++都没试过,难得啊,所以记录,积累一下经验。
一、项目背景

生产者和消费者,操作kafka,使用的是pykafka库。消费者从kafka获取消息后,发送给业务处理服务,使用的是requests库。

二、工具
在此次过程中,使用过的工具或者库。
1)、memory_profiler库。
可以使用这个库监控内存的消耗,以及基于行的内存分析

2)、objgraph库。
可以显示占据python程序内存的前n个对象
可以统计一段时间内,对象增加减少的数量
可以以图片形式,展现对象的引用关系

3)graphviz工具
将objgraph库生成的dot文件生成图片

三、初次分析
使用python开发程序,内存泄露的几个基本原因:
1、存在循环引用,gc不能释放;
2、存在全局对象,该对象不断的变大,占据内存;
3、使用了c或者c++扩展,扩展内存溢出了;
初次分析的时候,先进行代码review,并没有发现存在循环引用的变量和全局对象,而且我也没有调用c或者c++扩展。所以以上三点排除。
使用memory_profiler库,将输出记录到文本中进行分析。

发现并没有内存增加的情况。
后面加上gc.collect()也没有效果。

四、二次分析
第一分析无果,然后当时没想到什么方法。只能暴力,注释一部分代码进行排查。发现注释掉将消息发送给业务服务的代码,内存不会持续增长。
怀疑是不是requests库的问题。然后进行google:requests memory leak,发现还真有这个问题。地址: https://github.com/requests/requests/issues/1685
他们的讨论非常精彩。不过上面的讨论是在pypy下,requests出现内存泄露。而我用的Cpython。而且后面的pypy版本也已经修复了这个bug。不过当时不信邪,
还是认为requests库有问题,所以改换成python3的urllib去发送信息,发现结果还是一样。然后网上一查,特么requests底层是封装url库的。已哭晕在厕所。

五、三次分析
第二次分析还是没有结果。只能看是不是有循环引用,导致内存泄露。使用objgraph查看引用和对象生成关系。
1、使用objgraph.show_most_common_types(limit=10),查看占据内存前10的对象变化关系;
一开始:


运行一段时间后:

发现没太大变化的对象。
2、使用objgraph.show_growth(),观察对象增长情况。

grep "^Message" 1.txt

发现Message对象不断的增加。
3、查看Message到底是何方妖怪。
1)使用pdb断点进行查看:

发现Message是pykafka的Message实列。
2)进行引用查看。

由于没有安装xdot,使用graphviz工具查看。

发现没有出现循环引用。
3、继续单点调试。

发现这个Message不断的变大。这时候怀疑是不是pykafka这个库导致的。然后一查。厉害了,我的哥 ,pykafka的balancedconsumer类有个参数 queued_max_messages
这个参数的意思balancedconsumer会为每个分区缓存消息,默认一个分区是2000条。如果一条消息是1M,只有一个分区的话,就缓存了2000M,对于不够内存的机器那就gg了。
至此,问题找到了。解决方法是将这个参数改小点就好了。
发布了63 篇原创文章 · 获赞 28 · 访问量 23万+

猜你喜欢

转载自blog.csdn.net/kelindame/article/details/73008487