「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」
问题描述
我需要通过flask搭一个深度学习的接口,由于深度学习耗时很长,flask的http服务是单向被动通讯,(客户端请求,服务器端响应)。客户端发来一个get请求,http服务在等待深度学习模型响应,如果数据量很大,连接一定会超时。
需求就变成,我需要写一个flask接口,返回response的同时,还需要运行深度学习模型,这里使用了多线程,其中我还用协程调一个内部的查询api,部署时用docker+uwsgi。变成了docker+uwsgi+flask+threading+gevent。
问题就出在了gevent和uwsgi上,昨天坑了我一天并且我还没有找到原因。
今天我就改成docker+gunicorn+flask+threading+gevent。成功部署之后,我就开始排查发现是gevent+uwsgi出的问题。下面做了点实验验证我的想法,如果实验部分不想看,建议拉到最后看结论部分。
PS . uwsgi 和 gunicorn 在windows安装基本不成功,建议使用docker或者linux系统,不然,努力半天就像什么都没干一样。
我本机是windows使用的是docker。结构如下
# main.py
import gevent
from gevent import monkey;monkey.patch_all()
from gevent.pool import Pool
import threading
from flask import Flask, request, jsonify
import time
from time import perf_counter
app = Flask(__name__)
# 模拟请求
def get_url(num):
time.sleep(num)
print(f"cost:{num}s")
def gevent_get(msg):
print(msg)
s=perf_counter()
pool=Pool(5)
threads=[pool.spawn(get_url,num) for num in [1,2,3,2,1,2,3,4,4,5]]
gevent.joinall(threads
@app.route("/test", methods=["post","get"])
def test():
t = threading.Thread(target=gevent_get, args=("开始",))
t.start()
return jsonify({"code": 500,"msg": "success"})
if __name__ == '__main__':
app.run(debug=True)
# test.py
import requests
URL="http://192.168.160.237:8000/test"
response=requests.get(URL).json()
print(response)
# uwsgi.ini
[uwsgi]
# app是run.py里面的Flask对象
module = main:app
# 对外提供 http 服务的端口
http = :8000
#指定工作进程
processes = 4
#主进程
master = true
#每个工作进程有2个线程
threads = 2
#指的后台启动 日志输出的地方
daemonize = uwsgi.log
#保存主进程的进程号
pidfile = uwsgi.pid
docker build创建之后,通过uwsgi启动
uwsgi uwsgi.ini
如果按正常逻辑,输出应该是下面的结果
开始
[pid: 632|app: 0|req: 1/1] 172.17.0.1 () {32 vars in 386 bytes} [Tue Feb 15 05:28:25 2022] GET /test => generated 29 bytes in 4 msecs (HTTP/1.1 200) 2 headers in 71 bytes (3 switches on core 0)
sleep:1s
sleep:1s
sleep:2s
sleep:2s
sleep:3s
sleep:2s
sleep:3s
sleep:4s
sleep:4s
sleep:5s
花费时间8.007732099999885
但是实际输出的结果,到“开始”就卡住了。也就是gevent没有用。
开始
[pid: 632|app: 0|req: 1/1] 172.17.0.1 () {32 vars in 386 bytes} [Tue Feb 15 05:28:25 2022] GET /test => generated 29 bytes in 4 msecs (HTTP/1.1 200) 2 headers in 71 bytes (3 switches on core 0)
我昨天弄了一天也没解决,因为上面的代码是我今天想通之后简化的,开发代码逻辑要复杂更多。后面我就生气了,我就改成gunicorn。结果还是出问题了。
# 我就改成gunicorn.py
# 绑定的ip与端口
bind = "0.0.0.0:8000"
# 进程数
workers = 4
# 指定每个进程开启的线程数
# threads = 2
# 工作模式
worker_class = 'gthread'
# 处理请求的工作线程数,使用指定数量的线程运行每个worker。为正整数,默认为1。
# worker_connections = 2000
# 设置pid文件的文件名,如果不设置将不会创建pid文件
pidfile = './gunicorn.pid'
# 要写入错误日志的文件目录。
errorlog = './log/gunicorn.error.log'
# 要写入的访问日志目录
accesslog = './log/gunicorn.access.log'
正常输出应该是这样:
gunicorn -c gunicorn.py main:app
开始
sleep:1s
sleep:1s
sleep:2s
sleep:2s
sleep:3s
sleep:2s
sleep:3s
sleep:4s
sleep:4s
sleep:5s
但这里通要还是会卡住
gunicorn -c gunicorn.py main:app
开始
经过我多轮百度,翻看博客,发现gunicorn worker_class = 'gthread'有玄机。
我修改成worker_class = 'gevent'之后就一切正常了。默认的"sync"也是正常的
于是,我灵机一动想着是不是uwsgi也是和gevent有冲突。百度了一下,果然在uwsgi中使用协程要加一句话。
uwsgi uwsgi.ini --gevent 100 --gevent-early-monkey-patch
期间我还做了一下,gunicorn worker_class和uwsgi会不会对多线程有影响,结果是没有什么影响。
# main.py
import threading
from flask import Flask, request, jsonify
import time
app = Flask(__name__)
def get_url(num):
for i in range(num):
time.sleep(0.5)
print(i)
@app.route("/test", methods=["post","get"])
def test():
num=10
t = threading.Thread(target=get_url, args=(num,))
t.start()
return jsonify({"code": 500,"msg": "success"})
if __name__ == '__main__':
app.run(debug=True)
补充
后续还会研究一下,把上面的gevent替换成thread在做相同实验。
结论
在使用gevent+flask+uwsgi,配置启动
uwsgi uwsgi.ini --gevent 100 --gevent-early-monkey-patch
hi在使用gevent+flask+gunicorn,配置中模式worker_class = 'gevent'
gunicorn -c gunicorn.py main:app