Uvicorn并发模型本质是异步I/O,依赖单事件循环驱动协程,非多线程;1个worker可扛数千QPS,前提是应用无同步阻塞操作。

Uvicorn的并发模型本质是异步I/O,不是多线程
Uvicorn本身不靠线程池或线程切换处理并发,它依赖单个事件循环(如uvloop)驱动成千上万个协程。每个HTTP请求被拆解为多个awaitable步骤(接收头、读体、调用应用、写响应),中间I/O阻塞点由事件循环统一调度,CPU不空转。这意味着:即使只用1个worker,也能轻松扛住数千QPS——前提是你的应用逻辑不写time.sleep()、不调用requests.get()这类同步阻塞操作。
- 错误现象:加了
--workers 8后RPS反而下降,延迟飙升 → 很可能应用里混入了同步IO,导致所有worker轮流卡死 - 真实场景:FastAPI路由中用
async def+await database.fetch_all()(配asyncpg)才能释放事件循环 - 性能影响:启用
--loop uvloop在Linux下可提升2–3倍吞吐,但Windows只能回退到asyncio,且uvloop不支持子进程重载(--reload时会报错)
workers参数不是越多越好,它解决的是CPU-bound瓶颈
当你发现单worker下CPU利用率长期低于40%,但请求延迟高、队列堆积,那问题大概率不在Uvicorn,而在下游(数据库慢、外部API超时、模型加载阻塞)。此时加--workers只是让多个进程一起等,还浪费内存。真正该加worker的场景是:应用里存在无法避免的CPU密集计算(如图像预处理、密码哈希),或者你明确观察到单核CPU跑满但QPS上不去。
- 推荐值:
--workers $(( $(nproc) * 2 + 1 ))是常见起点,但必须配合压测调整;M2LOrder类GPU推理服务反而常设--workers 1,靠异步批处理+模型缓存榨取GPU吞吐 - 容易踩的坑:每个worker都独立加载一次大模型或全局缓存,4个worker=4份内存占用;用
--limit-max-requests 1000可强制轮换进程,缓解内存泄漏 - 验证方法:用
htop看各worker进程CPU占比是否均衡,若某几个接近100%、其余接近0%,说明负载分配异常(可能是长连接未合理关闭)
连接层参数比worker数更能决定高并发表现
很多开发者盯着--workers调优,却忽略连接管理才是高并发的第一道闸门。Uvicorn默认--backlog 100,意味着内核TCP等待队列最多存100个SYN包;一旦瞬时连接激增,新连接直接被丢弃(客户端看到Connection refused)。而--timeout-keep-alive设得太长(如60秒),会让空闲连接长期占用工作者资源,挤占新请求空间。
Uvicorn 0.52.1 是 Python ASGI Web 服务器,提供异步应用服务及 WebSocket 支持,常用于 FastAPI、Starlette 等应用的开发和部署。本下载为官方 PyPI 通用 wheel 包,可使用 pip 安装,并由包管理器处理所需依赖。
- 关键配置:
--backlog 2048(匹配系统net.core.somaxconn)、--timeout-keep-alive 5(移动端/HTTP/2友好)、--limit-concurrency 100(防单worker过载) - WebSocket注意:
--ws-max-size默认1MB,大文件上传需显式调大,否则触发WebSocketDisconnect: code=1009 - 协议选择:
--http httptools比默认h11快约40%,但httptools不支持HTTP/2,要HTTP/2必须用--http h11 --http2(性能略降)
生产环境别裸跑Uvicorn,Gunicorn是稳压器
纯Uvicorn多worker模式缺乏优雅重启、健康检查、请求超时熔断等能力。Gunicorn作为进程管理器,用-k uvicorn.workers.UvicornWorker把Uvicorn“封装”成一个worker类型,既能保留ASGI异步优势,又能获得成熟进程治理能力。
- 典型命令:
gunicorn -k uvicorn.workers.UvicornWorker app:app --workers 4 --timeout 120 --graceful-timeout 60 - 为什么需要:
--timeout防长请求拖垮整个worker;--graceful-timeout确保旧连接处理完再杀进程;Gunicorn主进程还能自动拉起崩溃的Uvicorn worker - 复杂点在于:Gunicorn的
--workers和Uvicorn内部的--workers是两层概念,前者是进程数,后者在UvicornWorker里被忽略(除非你手动传入);别重复设置
实际部署时,最易被忽略的是应用层对异步特性的“信任”——你得真正在每一步都用await,而不是假装异步。Uvicorn再快,也救不了一个time.sleep(1)藏在async def里的路由。










