FastAPI 生产部署实战:Gunicorn + Uvicorn 与进程管理
在日常开发阶段,我们通常用 uvicorn main:app --reload 跑一个 FastAPI 应用就够用了。但一旦要上线到生产环境,单进程、无进程守护、缺少并发能力的开发服务器就远远不够了。本文系统讲解如何用 Gunicorn + Uvicorn worker 在生产环境中稳定运行 FastAPI,并覆盖 Worker 调优、优雅关闭、Docker 打包与 Nginx 反向代理等核心知识点。
为什么生产环境不能用 uvicorn 单进程
uvicorn main:app --reload 虽然方便,但它本质上是单进程、单事件循环,存在几个生产环境无法接受的问题:
- 单核利用:一个 Uvicorn 进程只能吃满一个 CPU 核,多核机器直接浪费。
- 没有进程守护:进程崩溃后不会自动重启,需要外部 supervisor。
- 没有 reload 之外的重载策略:无法做 zero-downtime 滚动重启。
--reload自身有性能开销:文件监听本身消耗资源,绝不能带到生产。
正确的做法是用一个进程管理器来拉起多个 Uvicorn worker。FastAPI 官方推荐的就是 Gunicorn 配合 uvicorn.workers.UvicornWorker。
Gunicorn + Uvicorn Worker 基本用法
Gunicorn 是一个成熟的 WSGI 进程管理器,但 FastAPI 是 ASGI 应用。好在 Uvicorn 提供了一个 Gunicorn worker 类,让 Gunicorn 能管理 ASGI 进程。
最小启动命令
假设应用入口是 main.py 中的 app 对象:
gunicorn main:app \
-w 4 \
-k uvicorn.workers.UvicornWorker \
-b 0.0.0.0:8000 \
--access-logfile - \
--error-logfile -
参数含义:
| 参数 | 说明 |
|---|---|
-w 4 |
启动 4 个 worker 进程 |
-k uvicorn.workers.UvicornWorker |
使用 Uvicorn 提供的 ASGI worker |
-b 0.0.0.0:8000 |
监听地址与端口 |
--access-logfile - |
访问日志输出到标准输出 |
--error-logfile - |
错误日志输出到标准错误 |
目录结构示例
一个典型的小型 FastAPI 项目结构如下:
myapp/
├── main.py # 应用入口,定义 app = FastAPI()
├── routers/
│ ├── users.py
│ └── items.py
├── core/
│ ├── config.py # 配置管理
│ └── database.py # 数据库连接
├── requirements.txt
└── Dockerfile
main.py 示例:
from fastapi import FastAPI
from routers import users, items
from core.config import settings
app = FastAPI(title=settings.APP_NAME)
app.include_router(users.router)
app.include_router(items.router)
Worker 数量怎么定
Gunicorn 官方给出的经验公式是:
workers = (2 * CPU 核数) + 1
但这是针对 WSGI 同步应用的经验值。FastAPI 是异步的,单个 worker 已经能通过事件循环处理大量并发 IO,因此不必盲目堆 worker 数量。一般建议:
- CPU 密集型任务较多(如同步调用 CPU 算法):用
(2 * CPU) + 1,并把同步任务丢到线程池(run_in_threadpool)。 - 纯 IO 密集型(数据库、HTTP 调用):worker 数可以等于 CPU 核数甚至更少,靠事件循环扛并发。
查看机器核数:
nproc
python3 -c "import os; print(os.cpu_count())"
不要忽视内存上限
每个 worker 是独立进程,会各自加载应用代码、模型、连接池。如果应用里加载了大模型或大字典,4 个 worker 就是 4 倍内存。生产前务必用 docker stats 或 htop 实测单 worker 内存占用,再反推合理的 worker 数。
异步与同步混用的坑
FastAPI 路由可以是 async def 也可以是普通 def:
async def路由在事件循环里直接执行,不要在里面写阻塞调用(如time.sleep、同步requests、同步文件 IO),否则会卡住整个 worker。- 普通
def路由会被 Uvicorn 自动丢到线程池(anyio.to_thread.run_sync),不会阻塞事件循环。
import time
import httpx
from fastapi import FastAPI
app = FastAPI()
@app.get("/sync-block")
async def bad_blocking():
# 危险:阻塞事件循环,整个 worker 卡住
time.sleep(2)
return {"ok": True}
@app.get("/async-correct")
async def good_async():
async with httpx.AsyncClient() as client:
resp = await client.get("https://httpbin.org/get")
return {"status": resp.status_code}
@app.get("/sync-def")
def sync_def():
# 普通函数,自动放到线程池,安全
time.sleep(2)
return {"ok": True}
如果必须在 async def 里调用阻塞库,可以用 run_in_threadpool:
from fastapi.concurrency import run_in_threadpool
@app.get("/safe-block")
async def safe_block():
await run_in_threadpool(time.sleep, 2)
return {"ok": True}
优雅关闭与超时控制
生产环境部署更新时,不希望旧请求被直接掐断。Gunicorn 提供了几个关键参数:
| 参数 | 默认 | 建议 | 说明 |
|---|---|---|---|
--graceful-timeout |
30 | 30 | worker 收到停止信号后,给多少秒完成现有请求 |
--timeout |
30 | 30-120 | 单个 worker 超过该时间未响应心跳就被强杀 |
--keep-alive |
2 | 5 | keep-alive 连接保持秒数 |
推荐的完整启动配置(写入 gunicorn_conf.py):
import multiprocessing
import os
bind = "0.0.0.0:8000"
workers = int(os.getenv("WEB_CONCURRENCY", multiprocessing.cpu_count() * 2 + 1))
worker_class = "uvicorn.workers.UvicornWorker"
timeout = 60
graceful_timeout = 30
keepalive = 5
# 日志
accesslog = "-"
errorlog = "-"
loglevel = "info"
# 预加载应用:节省内存、加快启动,但注意全局状态会被所有 worker 共享读
preload_app = True
启动:
gunicorn main:app -c gunicorn_conf.py
注意:
preload_app = True会在 fork worker 之前加载一次应用代码。好处是多个 worker 共享同一份导入的模块内存(写时复制),坏处是数据库连接池等需要在 fork 后重新建立,否则子进程会共享父进程的 socket 句柄导致错误。对于 Tortoise ORM、SQLAlchemy async 等,建议在 worker 启动钩子里重新初始化连接。
Docker 打包实践
把 FastAPI 打包成 Docker 镜像时,推荐多阶段构建 + 非 root 用户运行。
Dockerfile
# ---------- 构建阶段 ----------
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt
# ---------- 运行阶段 ----------
FROM python:3.11-slim
WORKDIR /app
# 拷贝依赖
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
ENV PYTHONUNBUFFERED=1
# 安装 uvicorn worker 依赖
# requirements.txt 应包含: fastapi, uvicorn[standard], gunicorn
EXPOSE 8000
CMD ["gunicorn", "main:app", \
"-w", "4", \
"-k", "uvicorn.workers.UvicornWorker", \
"-b", "0.0.0.0:8000", \
"--timeout", "60", \
"--graceful-timeout", "30"]
对应的 requirements.txt:
fastapi==0.111.0
uvicorn[standard]==0.30.1
gunicorn==22.0.0
构建并运行:
docker build -t myapp:latest .
docker run -d --name myapp -p 8000:8000 --env WEB_CONCURRENCY=4 myapp:latest
Nginx 反向代理
Gunicorn 直接对外暴露 8000 端口在生产中不推荐,前面通常加一层 Nginx 处理 TLS、静态文件、限流和 WebSocket 代理。
upstream fastapi_backend {
server 127.0.0.1:8000;
# 多机部署时加更多 server
# server 10.0.0.12:8000;
}
server {
listen 80;
server_name api.example.com;
# 静态文件直接由 Nginx 处理
location /static/ {
alias /var/www/myapp/static/;
expires 30d;
}
# 反向代理到 Gunicorn
location / {
proxy_pass http://fastapi_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket 支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 超时
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
}
关键点:
proxy_set_header X-Forwarded-Proto $scheme让 FastAPI 知道原始协议是 https,配合--proxy-headers使用。- WebSocket 路由必须设置
Upgrade和Connection头,否则握手失败。 - Uvicorn 启动时加
--proxy-headers --forwarded-allow-ips='*'(或在 Gunicorn 配置里),才能正确解析X-Forwarded-*。
平滑重启与日志管理
平滑重启
向主进程发送 SIGHUP,Gunicorn 会重新加载配置并逐个重启 worker,实现 zero-downtime:
kill -HUP $(cat /var/run/myapp.pid)
如果只更新了应用代码而不改配置,用 SIGUSR2 触发热重载(配合 --preload 时不可用)。
日志切割
Gunicorn 的访问日志直接输出到 stdout,在 Docker 中由 Docker 日志驱动收集;在裸机上建议用 logrotate:
/var/log/myapp/*.log {
daily
rotate 14
compress
missingok
notifempty
sharedscripts
postrotate
kill -HUP $(cat /var/run/myapp.pid) 2>/dev/null || true
endscript
}
常见问题速查
- worker 启动后立刻被杀:多半是
--timeout太短,应用启动慢(比如连数据库超时)。检查--timeout与启动耗时。 - 数据库连接报错
MySQL server has gone away:连接池里的连接被服务端断开。设置pool_recycle小于服务端wait_timeout。 - 内存持续上涨:检查是否有未关闭的
httpx.AsyncClient、未限流的队列、或循环引用。 - WebSocket 502:Nginx 没加
Upgrade/Connection头,或proxy_read_timeout太短。 preload_app后子进程共享了数据库连接:在 worker 的@app.on_event("worker_init")(Uvicorn)或 lifespan 中重新建立连接池。
小结
生产部署 FastAPI 的核心组合是 Gunicorn(进程管理)+ UvicornWorker(ASGI 执行)+ Nginx(反向代理)。掌握以下几点就能稳住大多数场景:
- 不要用
--reload上生产,用 Gunicorn 管理 worker。 - Worker 数量按 CPU 核数和内存上限权衡,异步应用不必盲目堆多。
async def里严禁阻塞调用,必要时用run_in_threadpool。- 用
--graceful-timeout和SIGHUP实现优雅关闭与平滑重启。 - Docker 打包用多阶段构建,前面加 Nginx 处理 TLS 与静态资源。
把这些配置固化到项目的 gunicorn_conf.py、Dockerfile 和 Nginx 配置里,部署就成了一件可重复、可回滚的工程化操作。
- 点赞
- 收藏
- 关注作者
评论(0)