gunicorn或uwsgi部署flask 内存泄漏控制

4,823 阅读4分钟

「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战」。

问题

通过gunicorn部署flask服务,该服务是调用深度学习模型去做预测。然后通常到第3,第4次查询的时候就会出现服务莫名其妙的挂断。经过排查发现算法服务启动的时候内存占用只有1G不到,倒是到第3次时,内存占用就超过4G,这说明flask或者gunicorn出现的内存泄漏,后续就做实验验证内存泄漏这个问题。顺道把uwsgi也做了

实验

环境:

python 3.7 Flask==2.0.2 gevent==21.12.0 gunicorn==20.1.0

这里的实验并不是真正内存泄漏,而是通过一个全局的list模拟代码中存在内存泄漏,并通过对gunicorn和uwsgi配置修改对内存泄漏进行限制,防止爆内存。

gunicorn + flask

为了模拟泄漏的过程,定义L去接受l。

每一次请求的L都会增加78M。

# main.py
from flask import Flask, request, jsonify
import time
import sys

app = Flask(__name__)
L=[]
@app.route("/test", methods=["post","get"])
def test():
        import sys
        l=[10000 for _ in range(10000000)
        L.append([l])
        return jsonify({"code": 200,"msg": "success"})

if __name__ == '__main__':
    app.run(debug=False)

这里使用的配置就是常规配置

# gunicorn.py 配置
# 绑定的ip与端口
bind = "0.0.0.0:8000"
# 进程数  
workers = 4
# 指定每个进程开启的线程数
threads = 2
# 工作模式
worker_class = 'gevent'
# # 处理请求的工作线程数,使用指定数量的线程运行每个worker。为正整数,默认为1。
# worker_connections = 100
# 设置pid文件的文件名,如果不设置将不会创建pid文件
pidfile = './gunicorn.pid'
# 要写入错误日志的文件目录。
errorlog = './log/gunicorn.error.log' 
# 要写入的访问日志目录
accesslog = './log/gunicorn.access.log' 

在上述配置下,先请求10次,20次,50次,100次,然后看一下内存变化。

次数占用内存
10809MiB
201.541GiB
503.797GiB
1007.558GiB

如果使用gunicorn配置中不对请求做限制,内存会存在一直占用

修改gunicorn 配置max_requests=20 每20次会重启一下服务。

# gunicorn.py 配置

#最大客户端并发数量,默认情况下这个值为1000。此设置将影响gevent和eventlet工作模式
max_requests=20
# max_requests_jitter
max_requests_jitter=2

次数占用内存
10872.6MiB
201.605GiB
502.282GiB
1001.231GiB

内存

次数限制占用内存未限制占用内存
10872.6MiB809MiB
201.605GiB1.541GiB
502.282GiB3.797GiB
1001.231GiB7.558GiB

每20次请求就会随机重启(0,max_requests_jitter)worker,通过这种方式就可以限制内存

我使用uwsgi 做了相同的实验

uwsgi + flask

flask代码相同

[uwsgi]

# app是run.py里面的Flask对象 
module = main:app
# 对外提供 http 服务的端口
http = :8000
#指定工作进程
processes    = 4
#主进程
master     = true
#每个工作进程有2个线程
threads = 2
#指的后台启动 日志输出的地方
daemonize    = uwsgi.log
#保存主进程的进程号
pidfile = uwsgi.pid
次数占用内存
10811.7MiB
201.544GiB
503.802GiB
1007.564GiB

修改uwsgi的配置

[uwsgi]
#设置工作进程使用虚拟内存超过N MB就回收重启
reload-on-as= 2048

内存

次数限制占用内存未限制占用内存
10810.8MiB811.7MiB
201.545GiB1.544GiB
501.922GiB3.802GiB
1001.917GiB7.564GiB

通过限制虚拟内存数就可以起限制内存作用

也可使用

limit-as

通过使用POSIX/UNIX的setrlimit()函数来限制每个uWSGI进程的虚拟内存使用数。

--limit-as 256

这个配置会限制uWSGI的进程占用虚拟内存不超过256M。如果虚拟内存已经达到256M,并继续申请虚拟内存则会使程序报内存错误,本次的http请求将返回500错误。

reload-on-rss

跟reload-on-as的效果类似,不过这个选项控制的是物理内存。你可以同时使用这2个选项:

uwsgi:
  reload-on-as: 128
  reload-on-rss: 96

结论

由于这里不是真正内存泄漏,因为正在的内存泄漏很难排查。

gunicorn的 max_requests

uwsgi的 reload-on-as,reload-on-rs,limit-as

这里通过web服务器的配置进行物理限制。

但是这里要注意,这里做物理限制,存在部分http请求,正好是重启的那个worker处理的,存在着请求丢失的可能性。但是正常来说丢失是可以接受的。

但是像我每一个请求都是一个起一个模型进行算法调用,如果出现worker重启,直接把我任务个挂了,我这边是不不能接受的。

整体来说 http 不太适合算法服务的搭建,不如grcp,websocket