业务背景
书接上文 gunicorn+fastapi 如何控制并发——其一 ,后续我又接到了模型服务化的需求,简单搭个demo服务支持业务验证和对比实验,后续我觉得使用 Nginx+gunicorn+FastAPI
这种办法实在太重了,有没有更简单的办法,可以暂时支撑一段时间?
其实我们这里使用一个全局变量加线程锁就可以解决这个问题,之前使用 gunicorn
是因为想是用框架的一些能力,只可惜实现并发限流及其困难。那么有没有什么比较简单的框架呢?后续调研到可以使用 uvicorn
,这是一种 ASGI 服务器框架,主要用于处理异步请求。因为``FastAPI使用的是ASGI标准,而之前使用的
gunicorn` 是用于处理同步请求的,使用的是 WSGI 标准,例如 Flask,Django等,两种协议之间差异参考这里。
我们甚至可以直接在python代码中启动服务,直接使用 python app.py
就可以启动HTTP服务:
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get("/hello")
def predict():
return "world"
if __name__ == "__main__":
uvicorn.run(
app="main:app",
host="0.0.0.0",
port=8000,
log_level="debug",
)
那如何实现控制并发呢,因为 uvicorn
是基于 ASGI 协议的服务框架,因此在接收请求时,不必等待worker 空闲,默认worker能够异步处理多个请求。因此如果我们只是支持:每个worker支持1个并发,那么可以通过线程锁的方式来判断当前work是否繁忙,例如:
import uvicorn
from threading import Lock
from fastapi import FastAPI, HTTPException
app = FastAPI()
lock = Lock()
import time
@app.get("/hello")
def predict():
acquired = lock.acquire(blocking=False)
if acquired:
try:
time.sleep(1)
finally:
lock.release()
return "world"
else:
raise HTTPException(
status_code=503,
detail="service busy",
)
if __name__ == "__main__":
uvicorn.run(
app="main:app",
host="0.0.0.0",
port=8000,
log_level="debug",
workers=10,
)
我们通过非阻塞的方式获取线程锁,然后判断是否能够拿到锁,如果不能则立刻返回503错误。而且如果模型可以支持异步流式,我们甚至可以控制worker的并发度。相比之前 Nginx+gunicorn+FastAPI
的方案,这种方法简直不要简单太多。