
本文详解 fastapi 代理多路视频流时请求阻塞的根本原因——浏览器对单域名并行连接数的硬性限制(如 chrome/firefox 默认为 6),并提供可落地的客户端隔离、http 客户端复用及服务端响应优化方案。
本文详解 fastapi 代理多路视频流时请求阻塞的根本原因——浏览器对单域名并行连接数的硬性限制(如 chrome/firefox 默认为 6),并提供可落地的客户端隔离、http 客户端复用及服务端响应优化方案。
在构建实时视频流代理服务时,开发者常遇到一个典型现象:当通过 FastAPI 的 StreamingResponse 同时代理 5 路 MJPEG 流时一切正常;一旦开启第 6 路,后续所有 HTTP 请求(包括其他 API 端点)立即停滞,直至关闭任一已打开的流标签。问题根源并非 FastAPI 或 Uvicorn 性能不足,而是客户端(浏览器)主动施加的连接上限。
? 浏览器并发连接限制是罪魁祸首
现代主流浏览器(Chrome、Firefox、Edge)对同一 hostname(如 localhost:8000 或 api.example.com)强制实施 最大 6 个并行 TCP 连接 的限制(参见 Browserscope Network Limits)。该限制属于 HTTP/1.1 协议层行为,由浏览器内核硬编码实现,与后端框架无关。
当你的前端页面在 6 个 <img alt="FastAPI 视频流并发瓶颈解析:浏览器连接限制与服务端优化方案" > 或 <video></video> 标签中同时请求 /get_stream?url=... 时,浏览器会为每个 URL 建立独立长连接。第 7 次请求将被无提示挂起(stalled),等待前 6 个连接中任一释放——而 MJPEG 流本质是永不结束的 multipart/x-mixed-replace 响应,导致连接长期占用。
✅ 验证方式:使用
httpx.AsyncClient编写 Python 脚本并发请求 10 路流,可稳定运行;但同一域名下用浏览器打开 7 个标签页必现阻塞。
?️ 解决方案:三步破局
1️⃣ 客户端层面:绕过浏览器连接池(推荐)
避免在单一域名下发起全部流请求。可行策略包括:
-
子域名分流:部署多个反向代理子域(如
stream1.example.com,stream2.example.com),DNS 轮询或 Nginx 负载均衡到同一 FastAPI 实例; -
端口隔离:启动多个 Uvicorn 实例(
--port 8001,--port 8002…),前端按需请求不同端口; - Service Worker 代理:利用浏览器 Service Worker 拦截流请求,复用单个 WebSocket 或 Fetch 连接分发多路帧数据(适合高级场景)。
2️⃣ 服务端层面:复用 HTTP 客户端(关键优化)
原代码中每次请求都新建 httpx.AsyncClient(),不仅开销大,更易触发连接耗尽。应全局复用客户端实例:
import uvicorn
import httpx
from fastapi import FastAPI, Depends
from fastapi.responses import StreamingResponse
app = FastAPI()
# ✅ 全局复用 HTTP 客户端(带连接池)
http_client = httpx.AsyncClient(
timeout=httpx.Timeout(30.0, read=60.0),
limits=httpx.Limits(max_connections=100, max_keepalive_connections=20)
)
@app.on_event("shutdown")
async def shutdown_event():
await http_client.aclose()
async def proxy_mjpeg_stream(url: str):
try:
async with http_client.stream("GET", url) as stream:
async for chunk in stream.aiter_bytes():
if chunk:
yield chunk
except httpx.ReadTimeout as e:
print(f"Read timeout for {url}: {e}")
raise
@app.get("/get_stream")
async def get_stream(url: str):
if not url:
return StreamingResponse(iter([]), status_code=400)
headers = {
"Content-Type": "multipart/x-mixed-replace; boundary=frame",
"Cache-Control": "no-cache",
"Connection": "keep-alive"
}
return StreamingResponse(proxy_mjpeg_stream(url), headers=headers)⚠️ 注意:
httpx.AsyncClient必须作为全局变量或依赖注入,禁止在路径函数内创建;务必注册on_event("shutdown")清理资源。
3️⃣ 协议升级:启用 HTTP/2(进阶)
若基础设施支持(Nginx ≥1.25 + TLS),配置 HTTP/2 可突破单域名连接数限制——HTTP/2 允许多路复用(multiplexing)于单个 TCP 连接,彻底规避 6 连接墙。需确保:
- 反向代理(如 Nginx)启用
http2并正确配置 TLS; - FastAPI/Uvicorn 运行于 HTTPS 模式(Uvicorn 支持
--ssl-keyfile/--ssl-certfile); - 浏览器地址栏显示 ? 标识且 DevTools → Network → Protocol 列显示
h2。
? 总结:定位比优化更重要
| 问题表象 | 真实原因 | 解决优先级 |
|---|---|---|
| 第 6 路流后所有请求 stalled | 浏览器单域名并发连接限制(HTTP/1.1) | ⭐⭐⭐⭐⭐ |
| 内存/CPU 持续升高 | 每次请求新建 httpx.AsyncClient 导致连接池失控 |
⭐⭐⭐⭐ |
| 流中断频繁 | 后端超时设置不合理或未处理上游断连 | ⭐⭐⭐ |
核心原则: 视频流是典型的「客户端驱动型瓶颈」场景。在投入复杂后端调优前,请先用 curl -v 或 Python 脚本验证是否为纯浏览器限制——这能节省 80% 的无效排查时间。FastAPI 本身完全胜任百路并发流代理,关键在于让客户端「不卡住」。


















