You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

FastAPI代理多路视频流至浏览器时出现HTTP请求阻塞问题

FastAPI代理多路视频流至浏览器时出现HTTP请求阻塞问题

看起来你的问题出在异步客户端的连接管理和Uvicorn的默认并发配置上。当代理多个长连接的MJPEG流时,几个关键的限制会导致第6个请求无法被处理,直到某个现有连接关闭。下面是具体的原因分析和解决方案:

核心原因分析

  1. Uvicorn单Worker的并发瓶颈:默认情况下Uvicorn以单Worker运行,虽然异步框架理论上支持大量长连接,但如果没有明确配置,可能会因为底层资源调度限制导致并发请求被阻塞。
  2. httpx.AsyncClient重复创建的资源浪费:你的代码里每个流请求都会新建一个httpx.AsyncClient实例,每个实例维护独立的连接池。对于长连接的MJPEG流来说,这不仅会浪费系统资源,还可能因为连接池的分散管理导致可用连接被快速耗尽。
  3. 长连接超时参数缺失:httpx的默认超时设置可能会间接引发连接未正确释放的问题,虽然这不是直接阻塞的原因,但会影响长连接的稳定性。

具体解决方案

1. 复用全局的httpx.AsyncClient

不要为每个流创建新的AsyncClient,而是初始化一个全局异步客户端复用连接池,这样能更高效地管理到目标流主机的连接,避免资源分散浪费。

修改后的代码:

import uvicorn
import httpx
from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()

# 全局复用的AsyncClient,应用启动时创建,关闭时销毁
client: httpx.AsyncClient | None = None

@app.on_event("startup")
async def startup_event():
    global client
    # 针对长连接设置无读取超时,连接超时设为10秒(可根据实际调整)
    client = httpx.AsyncClient(timeout=httpx.Timeout(None, connect=10.0))

@app.on_event("shutdown")
async def shutdown_event():
    global client
    if client:
        await client.aclose()

async def proxy_mjpeg_stream(url: str):
    global client
    if not client:
        raise RuntimeError("AsyncClient未初始化")
    try:
        async with client.stream("GET", url) as stream:
            async for chunk in stream.aiter_bytes():
                if chunk:
                    yield chunk
    except httpx.ReadTimeout as er:
        print(f'ERROR ReadTimeout - {url=} - {er}')
    except Exception as e:
        print(f'ERROR 意外异常 - {url=} - {e}')

@app.get("/get_stream")
async def get_stream(url: str):
    if not url:
        return {"error": "必须提供URL参数"}
    headers = {
        "Content-Type": 'multipart/x-mixed-replace; boundary=frame',
    }
    return StreamingResponse(proxy_mjpeg_stream(url), headers=headers)

if __name__ == "__main__":
    uvicorn.run(app)

2. 调整Uvicorn的并发配置

运行Uvicorn时通过参数增加Worker数量和并发限制,提升整体长连接处理能力:

uvicorn main:app --workers 4 --limit-concurrency 100
  • --workers 4:启动4个Worker进程,每个Worker独立处理请求,大幅提升并发承载能力
  • --limit-concurrency 100:设置每个Worker的最大并发任务数,确保能容纳更多长连接流

3. 优化httpx连接池配置(可选)

如果所有代理的流都指向同一主机,可以进一步调整连接池参数,优化连接复用效率:

client = httpx.AsyncClient(
    timeout=httpx.Timeout(None, connect=10.0),
    limits=httpx.Limits(
        max_connections=50,  # 全局最大连接数
        max_keepalive_connections=20,  # 最大保持活跃的连接数
        keepalive_expiry=300  # 连接保持活跃的超时时间(秒)
    )
)

4. 检查系统文件描述符限制

如果调整后仍有问题,可以检查系统的文件描述符限制(通过ulimit -n命令查看),默认限制过低的话可以临时调高:

ulimit -n 10240

验证效果

修改完成后,尝试代理更多的流请求,应该不会再出现第6个请求被阻塞的情况。如果还有问题,可以进一步排查目标流主机的连接限制,或者增加Uvicorn的Worker数量。

备注:内容来源于stack exchange,提问作者Eldellano

火山引擎 最新活动