问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

填坑总结:python内存泄漏排查小技巧

发布网友 发布时间:2024-09-29 02:49

我来回答

1个回答

热心网友 时间:2024-10-10 18:05

最近服务遇到了内存泄漏问题,运维同学紧急呼叫解决,于是在解决问题之余也系统记录了下内存泄漏问题的常见解决思路。

首先搞清楚了本次问题的现象:

服务在13号上线过一次,而从23号开始,出现内存不断攀升问题,达到预警值重启实例后,攀升速度反而更快。

服务分别部署在了A、B 2种芯片上,但除模型推理外,几乎所有的预处理、后处理共享一套代码。而B芯片出现内存泄漏警告,A芯片未出现任何异常。

思路一:研究新旧源码及二方库依赖差异

根据以上两个条件,首先想到的是13号的更新引入的问题,而更新可能来自两个方面:

自研代码

二方依赖代码

从上述两个角度出发:

一方面,分别用Git历史信息和BeyondCompare工具对比了两个版本的源码,并重点走读了下A、B两款芯片代码单独处理的部分,均未发现任何异常。

另一方面,通过pip list命令对比两个镜像包中的二方包,发现仅有pytz时区工具依赖的版本有变化。

经过研究分析,认为此包导致的内存泄漏的可能性不大,因此暂且放下。

至此,通过研究新旧版本源码变化找出内存泄漏问题这条路,似乎有点走不下去了。

思路二:监测新旧版本内存变化差异

目前python常用的内存检测工具有pympler、objgraph、tracemalloc 等。

首先,通过objgraph工具,对新旧服务中的TOP50变量类型进行了观察统计

objraph常用命令如下:

#全局类型数量objgraph.show_most_common_types(limit=50)#增量变化objgraph.show_growth(limit=30)

这里为了更好的观测变化曲线,我简单做了个封装,使数据直接输出到了csv文件以便观察。

stats=objgraph.most_common_types(limit=50)stats_path="./types_stats.csv"tmp_dict=dict(stats)req_time=time.strftime("%Y-%m-%d%H:%M:%S",time.localtime())tmp_dict['req_time']=req_timedf=pd.DataFrame.from_dict(tmp_dict,orient='index').Tifos.path.exists(stats_path):df.to_csv(stats_path,mode='a',header=True,index=False)else:df.to_csv(stats_path,index=False)

如下图所示,用一批图片在新旧两个版本上跑了1个小时,一切稳如老狗,各类型的数量没有一丝波澜。

此时,想到自己一般在转测或上线前都会将一批异常格式的图片拿来做个边界验证。

虽然这些异常,测试同学上线前肯定都已经验证过了,但死马当成活马医就顺手拿来测了一下。

平静数据就此被打破了,如下图红框所示:dict、function、method、tuple、traceback等重要类型的数量开始不断攀升。

而此时镜像内存亦不断增加且毫无收敛迹象。

由此,虽无法确认是否为线上问题,但至少定位出了一个bug。而此时回头检查日志,发现了一个奇怪的现象:正常情况下特殊图片导致的异常,日志应该输出如下信息,即check_image_type方法在异常栈中只会打印一次。

但现状是check_image_type方法循环重复打印了多次,且重复次数随着测试次数在一起变多。

重新研究了这块儿的异常处理代码。

异常声明如下:

抛异常代码如下:

问题所在

思考后大概想清楚了问题根源:

这里每个异常实例相当于被定义成了一个全局变量,而在抛异常的时候,抛出的也正是这个全局变量。当此全局变量被压入异常栈处理完成之后,也并不会被回收。

因此随着错误格式图片调用的不断增多,异常栈中的信息也会不断增多。而且由于异常中还包含着请求图片信息,因此内存会呈MB级别的增加。

但这部分代码上线已久,线上如果真的也是这里导致的问题,为何之前没有任何问题,而且为何在A芯片上也没有出现任何问题?带着以上两个疑问,我们做了两个验证:

首先,确认了之前的版本以及A芯片上同样会出现此问题。

其次,我们查看了线上的调用记录,发现最近刚好新接入了一个客户,而且出现了大量使用类似问题的图片调用某局点(该局点大部分为B芯片)服务的现象。我们找了些线上实例,从日志中也观测到了同样的现象。

由此,以上疑问基本得到了解释,修复此bug后,内存溢出问题不再出现。

进阶思路

讲道理,问题解决到这个地步似乎可以收工了。但我问了自己一个问题,如果当初没有打印这一行日志,或者开发人员偷懒没有把异常栈全部打出来,那应该如何去定位?

带着这样的问题我继续研究了下objgraph、pympler 工具。

前文已经定位到了在异常图片情况下会出现内存泄漏,因此重点来看下此时有哪些异样情况:

通过如下命令,我们可以看到每次异常出现时,内存中都增加了哪些变量以及增加的内存情况。

使用objgraph工具objgraph.show_growth(limit=20)

使用pympler工具

from pympler import tracker tr = tracker.SummaryTracker() tr.print_diff()

通过如下代码,可以打印出这些新增变量来自哪些引用,以便进一步分析。

gth=objgraph.growth(limit=20)forgtingth:logger.info("growthtype:%s,count:%s,growth:%s"%(gt[0],gt[1],gt[2]))ifgt[2]>100orgt[1]>300:continueobjgraph.show_backrefs(objgraph.by_type(gt[0])[0],max_depth=10,too_many=5,filename="./dots/%s_backrefs.dot"%gt[0])objgraph.show_refs(objgraph.by_type(gt[0])[0],max_depth=10,too_many=5,filename="./dots/%s_refs.dot"%gt[0])objgraph.show_chain(objgraph.find_backref_chain(objgraph.by_type(gt[0])[0],objgraph.is_proper_module),filename="./dots/%s_chain.dot"%gt[0])

通过graphviz的dot工具,对上面生产的graph格式数据转换成如下图片:

dot-Tpngxxx.dot-oxxx.png

这里,由于dict、list、frame、tuple、method等基本类型数量太多,观测较难,因此这里先做了过滤。

内存新增的ImageReqWrapper的调用链

内存新增的traceback的调用链:

虽然带着前面的先验知识,使我们很自然的就关注到了traceback和其对应的IMAGE_FORMAT_EXCEPTION异常。

但通过思考为何上面这些本应在服务调用结束后就被回收的变量却没有被回收,尤其是所有的traceback变量在被IMAGE_FORMAT_EXCEPTION异常调用后就无法回收等这些现象;同时再做一些小实验,相信很快就能定位到问题根源。

另,关于 python3中 缓存Exception导致的内存泄漏问题,知乎有一篇讲的相对更清楚一点:https://zhuanlan.zhihu.com/p/38600861

至此,我们可以得出结论如下:由于抛出的异常无法回收,导致对应的异常栈、请求体等变量都无法被回收,而请求体中由于包含图片信息因此每次这类请求都会导致MB级别的内存泄漏。

另外,研究过程中还发现python3自带了一个内存分析工具tracemalloc,通过如下代码就可以观察代码行与内存之间的关系,虽然可能未必精确,但也能大概提供一些线索。

importtracemalloctracemalloc.start(25)snapshot=tracemalloc.take_snapshot()globalsnapshotgc.collect()snapshot1=tracemalloc.take_snapshot()top_stats=snapshot1.compare_to(snapshot,'lineno')logger.warning("[Top20differences]")forstatintop_stats[:20]:ifstat.size_diff<0:continuelogger.warning(stat)snapshot=tracemalloc.take_snapshot()

参考文章

https://testerhome.com/articles/19870?order\_by=created\_at&

https://blog.51cto.com/u\_3423936/3019476

https://segmentfault.com/a/1190000038277797

https://www.cnblogs.com/zzbj/p/13532156.html

https://drmingdrmer.github.io/tech/programming/2017/05/06/python-mem.html

https://zhuanlan.zhihu.com/p/38600861

本文分享自华为云社区《python内存泄漏排查小技巧》,作者:lutianfei。

声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
大学入学应该带哪些必备物品? tim删除聊天记录的方法步骤 pc端彻底删除tim聊天记录 甩脂机调到什么档位 如何用甩脂机 甩脂机的正确使用方法 使用抖脂机要注意什么 怎样申请小号微信号 ...的长和宽分别增加6米,扩建后草坪的面积增加了多少 ...扩建后长和宽分别增加10米,扩建后的操场面积增加了多少平方米?_百 ... 长方形的公园它的面积长和宽分别扩大十米后它的平方增加了面积增加了13... DNF信用分提升技巧!如何提升信用分? 我下的生存进化方舟怎么不能玩出现这个,然后就退掉了?我是win10_百度... 全民k歌怎么刷粉丝啊? 苏教版必修3语文的文言文 ...双侧卵巢内见数枚卵泡发育,左侧较大约0.8×0.7,右侧为1.4×_百度... ...无回声区,右侧最大0.8cmx0.7cm,左侧最大1.0x0.9cm是什么意思... 人流后半月复查后壁似查件见0.8x0.7稍弱回声区指 什么? 宫腔内见0.8x0.7液性暗区,周围见强回声回绕什么意思 劳力士满天星介绍劳力士满天星报价多少钱 美团滴滴打车怎么打 工匠物语怎么批量锻造 批量锻造方法介绍-新手攻略-安族网 工匠物语SmithStory手游怎么批量锻造介绍_工匠物语SmithStory手游怎么批... 了这次的月经,吃了黄体酮和妇科止血灵大概多长时间 西山居游戏有哪些 QQ飞车手游魔域传说套装怎么得介绍_QQ飞车手游魔域传说套装怎么得是什... 国产液晶电视所有器件中国会做吗 oppo的mp4的s9和s19那款性价比比较高,报一下价位,用过的来说下!_百度... 中国在用的液晶电视的液晶这东西都是考国外进口的是吗?为什么不自己生 ... 郑晓云个人履历 郑晓云主要著作 刚发现了一个很奇怪的现象,把磁铁靠近移动电源,它就会吸附住,请问... 最近遇到了一个很奇怪的现象,就是wifi老不正常,是邻居家的网,原来24... 我发现一个很奇怪的现象,女生的聊天话题里总有男生,但男生的聊天话题里... 播放软件哪种比较好?是MPC 还是KMPLAYER ? 我需要能够播放大多数视频... 现在比较好的影音播放器是什么啊“? kmplayer与射手影音与暴风影音的比较 好怕自己一事无成,学的是建筑,如果不做这个我能做什么呢,最近吃饭睡 ... ...虚拟IP,比如192.168.1.239,做个代理服务器,实现多台机器上网。谁能... 浙江鼎昆投资管理有限公司怎么样? 大家电视剧《夜雨》又名《给我一支烟》中小玉这个人物作何评价?请各... 如果我的手机号不用了,我还可以用这个帐号吗 世界名牌啤酒前十名 十大世界顶级啤酒 高职高专英语系列:新目标英语(Ⅱ)本书内容简介 四川绵阳用什么英语课本,初中和小学,请告知出版社,要2014年的,学校有什... 昨晚吃了西瓜跟鸡肝然后就一直吐 余额宝存100万一个月收益多少 卡西欧5168怎么拆 买什么定投基金最好 定投股票基金还是指数 “汉代金吾千骑来”的出处是哪里