gunicorn vs uvicorn vs uvicorn workers:Python web 进程模型对比

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 journal
  • KillMode=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 导致游标错乱。postworkerfork hook 里重置 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 库。
精确评价 共 0 人评价
可复现性
可复现 · 0 不可复现 · 0
文风
文风流畅 · 0 文风晦涩 · 0
立场
支持 · 0 反对 · 0

登录后即可对本帖作出评价。

评论区 0 条 · 所有人可在此交流

登录后参与评论。

还没有评论,来说两句。