由每个API调用引起的Flask应用程序内存泄漏

由每个API调用引起的Flask应用程序内存泄漏,flask,memory-leaks,Flask,Memory Leaks,我的FlaskAPI有一个很小的内存泄漏,多次API调用都会导致我的应用程序达到内存限制并崩溃。我一直在试图弄清楚为什么一些内存没有被释放,但到目前为止还没有成功,我相信我确实知道其来源。谢谢你的帮助 不幸的是,我无法共享代码,但为了用英语描述它,我的flask应用程序提供了一个API端点,供用户执行以下操作(一次调用完成): 根据提供的ID从MongoDB中提取一些数据 根据返回的内容,使用库构建文档对象并将其保存到磁盘 最后,我将保存到磁盘的内容上传到S3存储桶,然后删除磁盘上的内容 据我所

我的FlaskAPI有一个很小的内存泄漏,多次API调用都会导致我的应用程序达到内存限制并崩溃。我一直在试图弄清楚为什么一些内存没有被释放,但到目前为止还没有成功,我相信我确实知道其来源。谢谢你的帮助

不幸的是,我无法共享代码,但为了用英语描述它,我的flask应用程序提供了一个API端点,供用户执行以下操作(一次调用完成):

  • 根据提供的ID从MongoDB中提取一些数据
  • 根据返回的内容,使用库构建文档对象并将其保存到磁盘
  • 最后,我将保存到磁盘的内容上传到S3存储桶,然后删除磁盘上的内容
  • 据我所知,使用内存使用量最大的两个领域是初始化Document对象和连接/保存到S3(分别为7MB和4.8MB)

    我所做的监视Python进程内存使用情况的工作是让psutils打印出在某些关键点使用的rss内存(下面的示例代码)

    提供的控制台映像是在我在本地托管应用程序时调用应用程序三次之后提供的

    值得关注的是,每个连续的API调用似乎开始于或高于上次调用留下的内存使用量,并继续增加。该应用程序开始于93MB(见黄色突出显示),但在第一次调用后,它结束于103.79MB,第二次开始于103.87MB,结束于105.39MB,第三个从105.46Mb开始,到106MB结束。使用量在减少,但在100次调用之后,我仍然看到内存使用量在增加。红色和蓝色的线显示了API调用过程中不同点的内存更改。红线在文档构建之后,蓝线在S3上传之后

    请注意,我的测试程序每次都使用相同的参数调用API

    除其他外,我测试了以下各项:

  • gc.collect()
  • 使用“del”显式删除变量/对象引用
  • 确保mongo连接已关闭(因为我使用的是S3连接,我不知道是否有方法显式关闭此连接)
  • 没有我在每次API调用中保存到的全局变量(app是唯一的全局变量)
  • 我知道,因为我不能提供代码,这里可能没有太多的东西可以做,但如果没有想法,我想知道是否有一种最佳实践方法来处理烧瓶内存使用情况,或者在烧瓶函数返回某些内容后清除内存。现在,我的flask函数是相对标准的Python函数(因此我希望该函数中的局部变量在之后被垃圾收集)

    我正在使用Python 3.6、Flask 0.11.1和pymongo 3.6.1,我的测试现在在windows 7机器上进行,但我的IBM云服务器也遇到了同样的问题。

    重要注意事项 因为有人问了这个问题,关于如何修复Flask中的内存泄漏。这是对他的战略的总结

    最小示例 假设您有一个简单的无状态Flask应用程序,其中只有一个端点名为“foo”。请注意,其他端点“内存”和“快照”不是原始应用程序的一部分。我们需要他们稍后找到内存泄漏

    导入gc
    导入操作系统
    导入tracemalloc
    导入psutil
    从烧瓶进口烧瓶
    app=烧瓶(名称)
    全局变量=[]
    process=psutil.process(os.getpid())
    tracemalloc.start()
    s=无
    def_get_foo():
    全局变量
    全局变量append([1,“a”,3,True]*10000)#这是我们(放大的)内存泄漏
    返回{'foo':True}
    @app.route(“/foo”)
    def get_foo():
    gc.collect()#没有帮助
    return\u get\u foo()
    @app.route(“/memory”)
    def print_memory():
    返回{'memory':process.memory_info().rss}
    @app.route(“/snapshot”)
    def snap():
    全球s
    如果不是,则:
    s=tracemalloc.take_snapshot()
    返回“拍摄的快照\n”
    其他:
    行=[]
    top_stats=tracemalloc.take_snapshot()。将_与(s,'lineno')进行比较
    对于排名靠前的统计数据[:5]:
    行。追加(str(stat))
    返回“\n”。连接(行)
    如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
    app.run()
    
    内存泄漏在第17行,并由注释指示。不幸的是,这种情况很少发生

    如您所见,我已尝试通过手动调用垃圾回收来修复内存泄漏,即在端点“foo”处返回值之前调用gc.collect()。但这并不能解决问题

    查找内存泄漏 为了查明是否存在内存泄漏,我们多次调用端点“foo”,并测量API调用前后的内存使用情况。另外,我们将拍摄两张
    tracemalloc
    快照。是跟踪Python分配的内存块的调试工具。如果使用Python3.4+,则它位于标准库中

    以下脚本应阐明该策略:

    导入请求
    #预热,这样就不会测量烧瓶内部内存使用情况
    对于范围(10)内的uu:
    请求。获取('http://127.0.0.1:5000/foo')
    #API调用前的内存使用
    resp=requests.get('http://127.0.0.1:5000/memory')
    打印(f'Memory-before-API调用{int(resp.json().get(“Memory”))})
    #获取第一个内存使用快照
    resp=requests.get('http://127.0.0.1:5000/snapshot')
    #启动一些API调用
    对于范围(50)内的:
    请求。获取('http://127.0.0.1:5000/foo')
    #之后的内存使用情况
    resp=requests.get('http://127.0.0.1:5000/memory')
    打印(f'Memory after-API调用:{int(resp.json().get(“Memory”))})
    #拍摄第二张快照并打印结果
    resp=requests.get('http://127.0.0.1:5000/snapshot')
    pprint(分别为文本)
    
    输出:

    Memory before API call 35328000
    Memory after API call: 52076544
    ('.../stackoverflow/flask_memory_leak.py:17: '
     'size=18.3 MiB (+15.3 MiB), count=124 (+100), average=151 KiB\n'
     '...\\lib\\tracemalloc.py:387: '
     'size=536 B (+536 B), count=3 (+3), average=179 B\n'
     '...\\lib\\site-packages\\werkzeug\\wrappers\\base_response.py:190: '
     'size=512 B (+512 B), count=1 (+1), average=512 B\n'
     '...\\lib\\tracemalloc.py:524: '
     'size=504 B (+504 B), count=2 (+2), average=252 B\n'
     '...\\lib\\site-packages\\werkzeug\\datastructures.py:1140: '
     'size=480 B (+480 B), count=1 (+1), average=480 B')
    API调用前的内存35328000
    API调用后的内存:52076544
    (“…/stackoverflow/flask_memory_leak.py:17:”
    '大小=18.3兆字节(+15.3兆字节),计数=124(+100),平均值=151千字节\n'
    '…\\lib\\tracemalloc.py:3
    
    Memory before API call 35328000
    Memory after API call: 52076544
    ('.../stackoverflow/flask_memory_leak.py:17: '
     'size=18.3 MiB (+15.3 MiB), count=124 (+100), average=151 KiB\n'
     '...\\lib\\tracemalloc.py:387: '
     'size=536 B (+536 B), count=3 (+3), average=179 B\n'
     '...\\lib\\site-packages\\werkzeug\\wrappers\\base_response.py:190: '
     'size=512 B (+512 B), count=1 (+1), average=512 B\n'
     '...\\lib\\tracemalloc.py:524: '
     'size=504 B (+504 B), count=2 (+2), average=252 B\n'
     '...\\lib\\site-packages\\werkzeug\\datastructures.py:1140: '
     'size=480 B (+480 B), count=1 (+1), average=480 B')
    
    import sys
    import os
    import site
    from waitress import serve
    dir_path = os.path.dirname(__file__)
    sys.path.append(os.path.abspath(dir_path))
    venv_packages =  os.path.abspath(os.path.join(dir_path, 'venv', 'lib', 'site-packages'))
    sys.path.append(venv_packages)
    site.addsitedir(venv_packages)
    from dotenv import load_dotenv
    dotenv_path = os.path.join(os.path.dirname(__file__), '.env')
    load_dotenv(dotenv_path)
    from settings import API_HOST, API_PORT
    from app import app as application
    serve(application, host=API_HOST, port=API_PORT)
    
    . venv/bin/activate
    pip install waitress
    python app.waitress
    
    py -3 -m pip install waitress
    py app.waitress
    
    Python 3.7.9
    waitress 1.4.1
    Flask 1.1.2
    Flask-Cors 3.0.10
    Flask-JWT-Extended 3.25.0
    python-dotenv 0.10.3