fastapi 如何控制并发——其一

业务背景

先说结论,单独靠 gunicorn+fastapi 很难实现并发控制,注意这里的并发控制有特殊的含义:

假如并发设置为8,那么我们预期的结果是,如果当前已经有8个请求正在处理,那么立刻拒绝掉期间收到的其他请求,或者能够自行控制请求的等待时间。

这里的业务场景是:单机只启动一个进程,也就是 gunicorn:worker=1 ,同时只能只处理一个请求,其他的请求全部拒绝,而不进行排队。

我们使用了 fastapi 实现 HTTP 服务:

# -*- coding: utf-8 -*-
from fastapi import FastAPI
import time

app = FastAPI()

@app.get("hello")
async def echo_feature():
    time.sleep(6)
    print("hello")

尝试过的路径

我们尝试了以下几种方法:

排队超时

事实上,gunicorn 本身并没有排队机制,issue1492 issue1190 上说明了这点,gunicorn 只会将请求的socket挂起,等待空闲的 worker,因此不存在可设置的排队超时参数,而 timeout 以及 max_request 都是为了避免后端服务阻塞或者内存泄露而重启worker的参数。

套接字限制

有些方法提到了使用 backlog 来设置 gunicorn 能够挂起的最大连接数,也就是这些连接会处于 TIME_WAIT 状态,理论上如果当前设置 backlog=1 ,每次只会有一个请求正在等待连接,但事实远不如预期,操作系统会平衡 backlog 的长度以及丢弃请求的频率,issue1190 讨论了这个问题。并且,TCP底层有自己的重试机制,因此不会立刻向客户端报错,在我这里测试结果是,需要200ms客户端才能收到 dail tcp timeout 错误。

业务实现并发控制

后来我们想不让 gunicorn 进行并发控制,我们在 worker 层面通过线程锁或者线程安全计数器等实现并发控制,但我们发现想让gunicorn 把多个请求打到一个 worker 上,需要让 worker 支持异步。众所周知啊,python的异步只能用在io阻塞时,例如:

import asyncio
import logging
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from fastapi import FastAPI

app = FastAPI()
app.state.running = False

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"],
)


@app.middleware("http")
async def limit_requests_middleware(request, call_next):
    if app.state.running:
        print("reject request, cause app is running")
        return JSONResponse(status_code=503, content={"message": "Service unavailable"})
    app.state.running = True
    try:
        response = await call_next(request)
        return response
    finally:
        app.state.running = False


@app.get("/hello")
async def echo_feature():
    await asyncio.sleep(6)
    print("hello")

这里使用了 middleware 查询 worker 的状态,简单实现控制单 worker 并发为1,并且能够在 worker 繁忙时,立刻拒绝其他的请求。

对于计算密集型的服务,python的异步就是笑话,因此这种方法也被pass了。

这里可以将 await asyncio.sleep(6) 修改成同步的休眠: time.sleep(6) ,会发现请求还是会阻塞等待。

Flask反向代理

最终还是选择了Flask作为反向代理实现并发控制,但是期间也不见得很顺利。一开始,我们想通过Flask自身的限流模块来实现并发控制,例如:

http {
    limit_conn_zone all zone=conn_limit:10m;

    server{
        listen 80;
        location / {
            limit_conn conn_limit 1;
            proxy_pass http://0.0.0.0:8080;
        }
    }
}

但遗憾的是 Flask 并没有将超过并发数的请求排队timeout暴露出来,比如 limit_reqnodelay 设置,因此直接使用 limit_conn 似乎走不通。那就换个思路,限制连接数总可以吧,OK,我们设置连接数:

events {
    use epoll;
    worker_connections  1;
}

那这里的 worker_connections 是不是设置成 1呢?其实不然,这里的 worker_connections 指的是单个工作进程能够同时打开的连接数:

Sets the maximum number of simultaneous connections that can be opened by a worker process.

It should be kept in mind that this number includes all connections (e.g. connections with proxied servers, among others), not only connections with clients. link

当然也包括代理服务和后端服务之间的连接,以及代理服务和客户端之间的连接:

如果设置成2,nginx能够正常运行,但是任意请求都会出先 dail tcp fail,我猜测是 nginx 用于监听 worker 的连接,不太确定;如果设置成3,客户端会得到 nginx 给出的500错误,nginx错误日志显示:

worker_connections are not enough while connecting to upstream

也就是客户端能够与nginx worker 创建连接,但是由于连接数不够,worker不能与后端服务建立连接,因此我们需要设置 worker_connections 4;

超过并发数的连接,nginx会主动关闭连接,客户端将会收到 Post "http://0.0.0.0:80": EOF 的错误。

至此问题得到解决,解决方案确实不够优雅 :<

  • 17
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FastAPI是一个现代、快速(高性能)的Web框架,它使用最新的ASGI标准。Gunicorn是一个使用WSGI标准的应用服务器,它可以直接运行Flask和Django,但不能直接运行FastAPI。为了在部署FastAPI时使用Gunicorn,可以借助uvicorn工作器。可以使用以下命令来启动FastAPI应用程序: ``` gunicorn main:app --workers 2 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000 ``` 这个命令中,`main`是你的应用程序的入口文件名,`app`是FastAPI应用程序的实例名。`--workers 2`表示使用2个工作进程,`--worker-class uvicorn.workers.UvicornWorker`表示使用uvicorn工作器,`--bind 0.0.0.0:8000`表示绑定到0.0.0.0的8000端口。这样就可以使用Gunicorn和uvicorn工作器来部署FastAPI应用程序了。123 #### 引用[.reference_title] - *1* *2* [ubuntu+nginx+gunicorn+uvicorn+fastapi](https://blog.csdn.net/qq_41960127/article/details/128832617)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}} ] [.reference_item] - *3* [Docker生产环境部署FastAPI+supervisor+gunicorn+nginx](https://blog.csdn.net/weixin_45457042/article/details/129213133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值