Uvicorn和Gunicorn+Uvicorn有什么区别?

47
什么是使用Uvicorn部署FastAPI应用程序和Tiangolo的Gunicorn + Uvicorn容器化的区别?为什么我的结果表明,仅使用Uvicorn部署比Gunicorn + Uvicorn获得更好的结果?
当我在Tiangolo的文档中搜索时,它说:
您可以使用Gunicorn来管理Uvicorn并运行多个并发进程。这样,您就可以得到最佳的并发性和并行性。
从这个说明中,我可以假设使用Gunicorn会获得更好的结果吗?
这是我使用JMeter进行测试的结果。我将我的脚本部署到Google Cloud Run,并且这是结果:
使用Python和Uvicorn:

enter image description here

使用Tiangolo的Gunicorn+Uvicorn:

enter image description here

这是我的Python(Uvicorn)Dockerfile:

FROM python:3.8-slim-buster
RUN apt-get update --fix-missing
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y libgl1-mesa-dev python3-pip git
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
COPY ./requirements.txt /usr/src/app/requirements.txt
RUN pip3 install -U setuptools
RUN pip3 install --upgrade pip
RUN pip3 install -r ./requirements.txt --use-feature=2020-resolver
COPY . /usr/src/app
CMD ["python3", "/usr/src/app/main.py"]

这是我的Dockerfile,用于Tiangolo的Gunicorn+Uvicorn:

FROM tiangolo/uvicorn-gunicorn-fastapi:python3.8-slim
RUN apt-get update && apt-get install wget gcc -y
RUN mkdir -p /app
WORKDIR /app
COPY ./requirements.txt /app/requirements.txt
RUN python -m pip install --upgrade pip
RUN pip install --no-cache-dir -r /app/requirements.txt
COPY . /app

你可以从Tiangolo的Gunicorn+Uvicorn中看到错误。这是由Gunicorn引起的吗?

已编辑。

所以,在我的情况下,我使用惰性加载方法来加载我的机器学习模型。这是我的类来加载模型。

class MyModelPrediction:
    # init method or constructor
    def __init__(self, brand):
        self.brand = brand

    # Sample Method
    def load_model(self):
        pathfile_model = os.path.join("modules", "model/")
        brand = self.brand.lower()
        top5_brand = ["honda", "toyota", "nissan", "suzuki", "daihatsu"]

        if brand not in top5_brand:
            brand = "ex_Top5"
            with open(pathfile_model + f'{brand}_all_in_one.pkl', 'rb') as file:
                brand = joblib.load(file)
        else:
            with open(pathfile_model + f'{brand}_all_in_one.pkl', 'rb') as file:
                brand = joblib.load(file)

        return brand

这是我的API的终端节点。

@router.post("/predict", response_model=schemas.ResponsePrediction, responses={422: schemas.responses_dict[422], 400: schemas.responses_dict[400], 500: schemas.responses_dict[500]}, tags=["predict"], response_class=ORJSONResponse)
async def detect(
    *,
    # db: Session = Depends(deps.get_db_api),
    car: schemas.Car = Body(...),
    customer_id: str = Body(None, title='Customer unique identifier')
) -> Any:
    """
    Predict price for used vehicle.\n
    """
    global list_detections
    try:
        start_time = time.time()
        brand = car.dict()['brand']
        obj = MyModelPrediction(brand)

        top5_brand = ["honda", "toyota", "nissan", "suzuki", "daihatsu"]
        if brand not in top5_brand:
            brand = "non"

        if usedcar.price_engine_4w[brand]:
            pass
        else:
            usedcar.price_engine_4w[brand] = obj.load_model()
            print("Load success")

        elapsed_time = time.time() - start_time
        print(usedcar.price_engine_4w)
        print("ELAPSED MODEL TIME : ", elapsed_time)

        list_detections = await get_data_model(**car.dict())

        if list_detections is None:
            result_data = None
        else:
            result_data = schemas.Prediction(**list_detections)
            result_data = result_data.dict()

    except Exception as e:  # noqa
        raise HTTPException(
            status_code=500,
            detail=str(e),
        )
    else:
        if result_data['prediction_price'] == 0:
            raise HTTPException(
                status_code=400,
                detail="The system cannot process your request",
            )
        else:
            result = {
                'code': 200,
                'message': 'Successfully fetched data',
                'data': result_data
            }

    return schemas.ResponsePrediction(**result)

1
你的表中的平均/最小/最大值是多少?响应时间呢? - Gino Mempin
@Gino 是的,这是我得到的平均/最小/最大响应时间。 - MADFROST
你能否也发布一个你用于测试的端点示例?我假设你的测试会多次命中某个端点。此外,了解Gunicorn是用来并行化你的进程,使用工作进程。它与并发不同。请参见https://fastapi.tiangolo.com/async/。 - Gino Mempin
@GinoMempin 我已经更新了我的问题,所以在我的端点中使用Async def。那么async def不适合并行处理吗? - MADFROST
@GinoMempin 好的,当我阅读文档时,它解释说如果你正在使用机器学习模型,你可以使用并发+并行,它建议使用async defawait。如果我们参考之前解释过的tiangolo-gunicorn-uvicorn,应该会很顺利,对吧? - MADFROST
请阅读 https://stackoverflow.com/help/minimal-reproducible-example。 - aaron
1个回答

58

Gunicorn 是一个与你的 Web 应用程序使用 WSGI 协议进行交互的应用服务器。这意味着 Gunicorn 可以为使用同步 Web 框架(如 Flask 或 Django,尤其是在 2021 年之前发布的版本)编写的应用程序提供服务。它的工作方式 是创建和维护一定数量的应用程序实例(工作进程),以响应来自客户端的 HTTP 请求。Gunicorn 主进程的作用是确保工作进程的数量与设置中定义的数量相同。因此,如果任何一个工作进程停止运行,主进程会启动另一个工作进程。 Gunicorn 本身与 FastAPI 不兼容,因为 FastAPI 使用了最新的 ASGI 标准。

Uvicorn 是一个支持 ASGI 协议的应用服务器。然而,作为一个工作管理器,它的能力还有很大的提升空间。

但是 Uvicorn 有一个 与 Gunicorn 兼容的工作类。 使用这种组合,Gunicorn 将充当工作管理器和接受传入 HTTP 请求的服务器。当请求到达工作进程时,这一层将确保在将数据传递给您的应用程序时实现 ASGI 兼容性。

如果您拥有一个具有 Kubernetes、Docker Swarm 或其他类似复杂系统的机器集群,用于管理多台机器上的分布式容器,那么您可能希望在集群级别处理复制,而不是在每个容器中使用进程管理器(如带有工作进程的 Gunicorn)。 像 Kubernetes 这样的分布式容器管理系统通常有一些集成的方式来处理容器的复制,同时仍然支持传入请求的负载均衡。所有这些都在集群级别进行。 在这些情况下,您可能希望从头开始构建一个 Docker 镜像,安装您的依赖项,并运行单个 Uvicorn 进程,而不是运行类似 Gunicorn with Uvicorn workers 的东西。


6
现在距离gunicorn支持ASGI的问题仍然开放已经有6年了(如果有人想要做出贡献,可以在这里找到相关信息:https://github.com/benoitc/gunicorn/issues/1380)。 - mirekphd
2
请注意,k8s的Pod并不是gunicorn worker的实际替代品。其中一个问题是:节点可以处理的Pod数量限制要比每个用户的进程数量限制低得多(250-1000与高达420万)。 - mirekphd
1
@mirekphd 而且gunicorn并不是唯一的。到了2023年,标准库中仍然存在没有async替代方案的阻塞调用。我想这更多地反映了async设计本身,而不是像gunicorn这样的工具。 - satoru
@mirekphd 你的例子是正确的。但我认为它并没有太多的含义。首先,我们不会使用太多的进程来导致垃圾处理。其次,对于一个Web服务器部署,我们可以拥有许多节点,因此最终可以达到100万个进程。 - whitehatboxer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接