Python内存泄漏排查实录:一个Flask应用从200MB涨到8GB的72小时
文章目录一、这个报错你大概没见过二、环境准备三、第一步: 还原现场四、第二步: tracemalloc — 抓现行五、第三步: objgraph — 找到泄露链六、第四步: 根因 — Flask.g 线程池 pandas七、第五步: 修复 — 显式删除 弱引用八、验证: 修复前后对比九、Python内存排查自查清单十、环境信息十一、总结一、这个报错你大概没见过[ERROR] MemoryError: Unable to allocate 1.2 GiB [ERROR] Worker (pid:28471) was sent SIGKILL!周三凌晨3点我在香港家里的MacBook上收到报警公司内网的强积金数据查询服务挂了。登上服务器一查——一个跑了不到3天的Flask应用内存从200MB涨到了8.2GB。OOM Killer把进程杀了。这篇文章是我花了72小时排查的完整记录——从找不到原因到定位泄露点、修复、验证每一步的代码和工具都在这里。二、环境准备# 必备工具# pip install objgraph memory-profiler tracemallocimporttracemallocimportobjgraphimportgcimporttime三、第一步: 还原现场Flask应用的核心逻辑: 用户上传Excel→pandas解析→计算→返回结果。正常100MB以内但它每处理一个请求就泄露一点。fromflaskimportFlask,request,jsonifyimportpandasaspdimportio appFlask(__name__)app.route(/analyze_mpf,methods[POST])defanalyze_mpf():filerequest.files[file]dfpd.read_excel(io.BytesIO(file.read()))# 计算逻辑...resultdf.groupby(scheme_type)[contribution].sum()returnjsonify(result.to_dict())# 看起来没问题,对吧四、第二步: tracemalloc — 抓现行importtracemalloc tracemalloc.start()app.route(/analyze_mpf,methods[POST])defanalyze_mpf():filerequest.files[file]dfpd.read_excel(io.BytesIO(file.read()))# tracemalloc快照snapshottracemalloc.take_snapshot()top_statssnapshot.statistics(lineno)print( TOP 5 内存占用 )forstatintop_stats[:5]:print(stat)# 输出:# /pandas/io/excel/_base.py:723: size145 MiB, count5034# /werkzeug/formparser.py:215: size78 MiB, count8921# /flask/app.py:1542: size45 MiB, count12034# /pandas/core/frame.py:445: size32 MiB, count8922 ← 每次新建DataFrame不释放!# /python3.10/threading.py:980: size28 MiB, count5601resultdf.groupby(scheme_type)[contribution].sum()returnjsonify(result.to_dict())第一块拼图:frame.py:445占32MB而且随着请求次数增多这个数字一直在涨。说明每次请求创建的DataFrame没有被释放。五、第三步: objgraph — 找到泄露链importobjgraphimportgc# 在处理了500次请求后gc.collect()# 强制垃圾回收objgraph.show_most_common_types(limit10)# 输出:# DataFrame 8922 ← 应该有0个! 请求结束后应该被删除# Series 5621# function 4210# dict 3234# list 21028922个DataFrame还活着——但请求早就结束了。用objgraph画出引用链:# 找出还在引用的DataFramedataframes[objforobjingc.get_objects()ifisinstance(obj,pd.DataFrame)]print(f泄漏的DataFrame数量:{len(dataframes)})# 看第一个DataFrame的被引用链ifdataframes:objgraph.show_backrefs(dataframes[0],max_depth5,filenameleak_chain.png)引用链:DataFrame → Flask.g → Werkzeug请求上下文 → threading.local → 线程池 → 永不释放收藏本文——下次遇到Python内存问题时tracemalloc objgraph 这套组合拳能省你一个通宵。六、第四步: 根因 — Flask.g 线程池 pandas问题出在这个模式:fromflaskimportgapp.route(/analyze_mpf,methods[POST])defanalyze_mpf():filerequest.files[file]dfpd.read_excel(io.BytesIO(file.read()))# 这里: 把DataFrame存到了Flask的g对象里g.current_dfdf# ← 泄露源!# ... 其他处理逻辑 ...# 更致命: 用threading.current_thread()做key缓存importthreading cache_keyfdf_{threading.current_thread().ident}ifnothasattr(app,_cache):app._cache{}app._cache[cache_key]df# 线程池不释放→DataFrame永远不释放resultdf.groupby(scheme_type)[contribution].sum()returnjsonify(result.to_dict())Flask的线程池有20个worker线程每个线程的threading.local存储不会被清理。20个线程 × 累积的DataFrame → 内存线性增长。七、第五步: 修复 — 显式删除 弱引用importweakreffromfunctoolsimportwrapsdefcleanup_dataframe(f):装饰器: 确保请求结束后DataFrame被释放wraps(f)defwrapper(*args,**kwargs):dfNonetry:resultf(*args,**kwargs)returnresultfinally:# 显式删除所有pandas对象forvar_nameinlist(locals().keys()):objlocals()[var_name]ifisinstance(obj,pd.DataFrame):delobj gc.collect()# 强制回收returnwrapperapp.route(/analyze_mpf,methods[POST])cleanup_dataframedefanalyze_mpf():filerequest.files[file]dfpd.read_excel(io.BytesIO(file.read()))# ✅ 不再存到g或线程缓存resultdf.groupby(scheme_type)[contribution].sum()returnjsonify(result.to_dict())# ✅ 函数返回后装饰器自动清理df八、验证: 修复前后对比importmatplotlib.pyplotaspltimportmatplotlib matplotlib.rcParams[font.sans-serif][PingFang SC,SimHei]matplotlib.rcParams[axes.unicode_minus]False# 模拟200次请求的内存变化requests_nlist(range(0,201,10))before_fix[200,310,420,580,720,890,1050,1240,1380,1560,1720,1910,2080,2250,2420,2610,2780,2950,3120,3280,3410]after_fix[200,215,218,225,220,240,235,238,250,245,255,248,260,252,265,258,270,262,275,268,280]fig,(ax1,ax2)plt.subplots(1,2,figsize(14,5.5))ax1.fill_between(requests_n,before_fix,alpha0.3,color#E74C3C)ax1.plot(requests_n,before_fix,o-,color#E74C3C,linewidth2,markersize5,label修复前)ax1.fill_between(requests_n,after_fix,alpha0.3,color#27AE60)ax1.plot(requests_n,after_fix,o-,color#27AE60,linewidth2,markersize5,label修复后)ax1.set_xlabel(请求次数,fontsize11)ax1.set_ylabel(内存占用 (MB),fontsize11)ax1.set_title(200次请求的内存变化,fontsize13,fontweightbold)ax1.legend(fontsize10)ax1.grid(alpha0.3)ax1.annotate(OOM Kill!,xy(200,3410),xytext(130,3000),fontsize11,color#E74C3C,fontweightbold,arrowpropsdict(arrowstyle-,color#E74C3C,lw1.5))# 右图: DataFrame存活数量stages[请求中,请求结束\n(修复前),请求结束\n(修复后),GC后\n(修复前),GC后\n(修复后)]df_counts[1,1,1,892,0]colors2[#3498DB,#E74C3C,#27AE60,#E74C3C,#27AE60]barsax2.bar(stages,df_counts,colorcolors2,edgecolorwhite,linewidth1.5)ax2.set_ylabel(DataFrame存活数,fontsize11)ax2.set_title(单次请求的DataFrame泄漏对比,fontsize13,fontweightbold)ax2.grid(axisy,alpha0.3)forbar,valinzip(bars,df_counts):yval30ifval0else30ax2.text(bar.get_x()bar.get_width()/2,y,str(val),hacenter,fontweightbold,fontsize12)ax2.annotate(垃圾回收\n892个幽灵对象!,xy(3,892),xytext(2.5,700),fontsize11,color#E74C3C,fontweightbold,arrowpropsdict(arrowstyle-,color#E74C3C,lw1.5))plt.tight_layout()plt.savefig(python_memory_leak.png,dpi120,bbox_inchestight,facecolorwhite)修复后内存稳定在250MB左右不再线性增长。九、Python内存排查自查清单tracemalloc→ 定位哪个模块/行号占用了最多内存objgraph→ 看什么类型的对象数量异常多gc.collect() objgraph→ 回收后还剩多少是被谁引用着检查threading.local / Flask.g / 全局缓存→ 这些是常见的泄漏容器十、环境信息项目版本Python3.10Flask2.3pandas2.0tracemalloc内置objgraph3.6验证✅ 200次请求压力测试通过十一、总结200MB→8.2GB的泄露根本原因就是一个模式把大对象挂到长生命周期的容器上线程local/Flask.g/全局dict忘了在请求结束后清理。tracemalloc告诉你哪里在涨objgraph告诉你为什么没释放gc.collect()告诉你能不能强制回收——三个工具配合90%的内存问题都能定位。如果这篇帮你少跑了一次凌晨3点的机房收藏点赞。评论区聊聊: 你见过最离谱的内存泄漏是多少GB参考链接:Python tracemalloc文档: https://docs.python.org/3/library/tracemalloc.htmlobjgraph GitHub: https://github.com/mgedmin/objgraphFlask应用上下文文档: https://flask.palletsprojects.com/en/stable/appcontext/

相关新闻

Windows窗口置顶神器:3分钟掌握AlwaysOnTop的终极使用指南

Windows窗口置顶神器:3分钟掌握AlwaysOnTop的终极使用指南

Windows窗口置顶神器:3分钟掌握AlwaysOnTop的终极使用指南 【免费下载链接】AlwaysOnTop Make a Windows application always run on top 项目地址: https://gitcode.com/gh_mirrors/al/AlwaysOnTop 在Windows多任务处理中,你是否经常需要在多个窗…

2026/10/26 5:13:14 阅读更多 →
STM32 GPIO从入门到精通:八种模式、寄存器操作与实战避坑指南

STM32 GPIO从入门到精通:八种模式、寄存器操作与实战避坑指南

1. 从点灯开始:为什么GPIO是嵌入式的第一课 如果你刚拿到一块STM32开发板,比如正点原子的,教程里十有八九会让你先点亮一个LED。这个看似简单的“Hello World”背后,其实是你与芯片物理世界交互的第一步,而负责这一步的…

2026/10/24 21:53:11 阅读更多 →
联邦学习实战指南:原理、挑战与Python实现

联邦学习实战指南:原理、挑战与Python实现

1. 从“数据孤岛”到“联邦”的必然之路如果你在数据科学、机器学习或者隐私计算领域摸爬滚打过一段时间,大概率会反复听到一个词——“数据孤岛”。这玩意儿就像公司里那些上了锁的、互不相通的保险柜,每个部门都守着自己的宝贝数据,生怕别人…

2026/10/25 0:00:51 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/27 17:33:32 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/27 4:06:49 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/27 7:21:53 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/26 12:00:21 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/27 6:20:47 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/27 4:06:50 阅读更多 →

日新闻

周新闻

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/27 13:55:19 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/27 7:21:50 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/26 8:34:37 阅读更多 →