Python web 应用部署最常见的问题:worker 数怎么定?sync 还是 async?
gunicorn vs uvicorn 选哪个?下面讲清楚。
1. WSGI vs ASGI
- WSGI:传统同步接口(Flask / Django 默认)
- ASGI:异步接口(FastAPI / Starlette / Django 4+ async)
WSGI 接口里每个请求一个线程同步处理;ASGI 一个 event loop 协程并发。
2. 几种部署组合
A. gunicorn + sync workers(WSGI 经典)
gunicorn -w 4 myapp:app
-w 4 起 4 个 worker 进程,每个进程同步处理一个请求。
WSGI app(Flask / Django)的默认。
并发上限 = workers 数。worker 数建议 2 × CPU + 1。
B. gunicorn + gthread
gunicorn -w 4 --threads 8 myapp:app
每个 worker 起 8 个线程,并发 = 4 × 8 = 32。
合适 IO 密集应用(Python 线程 GIL 不阻塞 IO)。
C. gunicorn + gevent / eventlet
gunicorn -w 4 -k gevent --worker-connections 1000 myapp:app
gevent monkey-patch socket → 每个 worker 处理 1000 并发协程。
极高并发但代码必须 monkey-patch 友好(少用 C 扩展)。
适用:Django / Flask 想要异步表现但不能完全重写。
注意:psycopg2 等 C 扩展跟 gevent 不友好,要用 psycogreen 包。
D. uvicorn(ASGI 推荐)
uvicorn myapp:app --workers 4 --host 0.0.0.0 --port 8000
uvicorn 起 4 个 worker,每个跑独立 event loop。
FastAPI / Starlette 项目默认。
E. gunicorn + uvicorn workers(生产 ASGI 推荐)
gunicorn myapp:app -w 4 -k uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:8000 \
--timeout 60 --keep-alive 5
gunicorn 管理 worker 生命周期(restart 优雅、信号处理稳),uvicorn
处理 ASGI 协议。FastAPI 生产推荐这个。
为什么不直接 uvicorn?gunicorn 的进程管理更成熟(worker 数、reload 信号、
preload、failover)。uvicorn 单进程模式 dev 用就好。
3. worker 数选择
CPU 密集(每个请求大量计算):workers = CPU 核数
IO 密集 + 同步代码(Flask):workers = 2 × CPU + 1(部分 worker 阻塞时
另一个能接客)
IO 密集 + async(FastAPI):workers = CPU 核数,依靠 event loop 内并发
具体数字看 profiling,不要拍脑袋。
4. 内存
每个 worker 独立内存。Django / 大型 app 一个 worker 可能 200-500 MB。
8 worker × 300 MB = 2.4 GB 起步。
--preload:在 fork 前加载 app 代码,worker 共享只读 page → 节省内存:
gunicorn --preload -w 4 myapp:app
副作用:app 启动慢,且某些资源(DB connection / cache)不能在 fork 前
初始化。生产开 preload 时常踩这个坑。
5. graceful restart
# Reload code without dropping requests
kill -HUP $(cat gunicorn.pid)
gunicorn 收到 HUP 重读代码,启动新 worker,等老 worker 处理完旧请求再退。
代码部署的标准操作。
uvicorn 单进程没有这个能力(要重启),所以生产基本是 gunicorn + uvicorn workers。
6. timeout
gunicorn --timeout 60 ...
请求处理超过 60s worker 被 kill。默认 30s。慢 endpoint 设大一点;
但太大会让卡死的 worker 长期占资源。
uvicorn worker 类型支持 --timeout-keep-alive、--limit-concurrency 等。
7. keepalive
gunicorn --keep-alive 5 ...
每条 TCP 连接保持 5s 等下一个请求。CDN / 反代后面建议 30-60s 减少 TCP
握手开销。
8. systemd unit
# /etc/systemd/system/myapp.service
[Unit]
Description=My FastAPI app
After=network.target
[Service]
Type=notify
User=app
Group=app
WorkingDirectory=/srv/myapp
EnvironmentFile=/srv/myapp/.env
# gunicorn + uvicorn worker
ExecStart=/srv/myapp/.venv/bin/gunicorn myapp.main:app \
-w 4 -k uvicorn.workers.UvicornWorker \
--bind 127.0.0.1:8001 \
--timeout 60 --keep-alive 30 \
--access-logfile - --error-logfile -
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=30
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
注意:
Type=notify:gunicorn 启动好之后 notify systemd--access-logfile -:日志到 stdout → systemd journalKillMode=mixed:先 SIGTERM 父进程,超时再 KILL 子进程
9. 前面反代(nginx)
upstream myapp {
server 127.0.0.1:8001;
# keepalive 减少 TCP 握手
keepalive 32;
}
server {
location / {
proxy_pass http://myapp;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
proxy_connect_timeout 5s;
}
}
proxy_http_version 1.1 + Connection "" 让 nginx → gunicorn 用长连接。
10. uvicorn 单独的场景
dev:uvicorn --reload(自动热加载 + 单进程)
小测试 / sidecar:uvicorn --workers 2 ...
生产不直接 uvicorn(信号处理 / preload / 优雅退出都不如 gunicorn)。
11. Hypercorn / Daphne 等其它 ASGI server
- Hypercorn:纯 Python,支持 HTTP/2 + HTTP/3
- Daphne:Django Channels 的官方推荐
- Granian:Rust 写的 ASGI,号称更快
uvicorn 是事实标准,其它有特定需求才考虑。
12. asgi 与 wsgi 共存
老 Django 项目想加 async 路由:用 ASGI server + django.core.asgi.get_asgi_application(),
sync 视图 / async 视图都能跑。
踩过的坑
- 数据库连接没在 fork 后重建:preload + gunicorn → 所有 worker 共享一个
DB connection 导致游标错乱。postworkerforkhook 里重置 DB pool。 - worker 数太多:CPU 都被 context switch 吃了。8 核机器开 64 worker 是反优化。
- timeout 太短:报告 / 导出大数据的 endpoint 几十秒被 kill。慢 endpoint
设单独 timeout 或异步化(Celery)。 - 异步框架里 await 一个同步 IO(pandas / requests / boto3)→ 阻塞 event
loop,其它请求全等。用run_in_executor或者换 async 库。
登录后参与评论。