Skip to content

FastAPI 生产部署

Gunicorn + Uvicorn 生产多进程模型、Lifespan 优雅停机、流式输出、后台任务与连接池调优。

Updated View as Markdown
For humans

FastAPI 生产部署

校招考察频度:★ 高频(企业级落地·生产部署实战) 生产导向主线:把 FastAPI 从本地 uvicorn main:app --reload 搬到大厂生产 Kubernetes 集群,不仅是敲几行启动命令那么简单。面试官高频必考:Gunicorn + Uvicorn 多进程 ASGI 运行模型与 Worker 核算、Lifespan 异步上下文管理器与优雅停机(Graceful Shutdown)、异步数据库连接池打满排查,以及生产 SSE 流式输出。


一、生产进程模型:Gunicorn + UvicornWorker

在单机或裸机部署时,Python 的 GIL 限制了单进程只能利用 1 个 CPU 核心。生产环境通常使用 Gunicorn 作为主进程管理器(Master),UvicornWorker 作为处理 ASGI 异步请求的工作进程(Worker):

# 生产经典启动命令
gunicorn main:app \
  -w 4 \
  -k uvicorn.workers.UvicornWorker \
  --bind 0.0.0.0:8000 \
  --timeout 120 \
  --keep-alive 5 \
  --access-logfile - \
  --error-logfile -
维度Gunicorn Master 进程职责Uvicorn Worker 进程职责
角色监工(Supervisor),不直接处理 HTTP 请求真正干活的 ASGI 异步工作单元
工作内容绑定监听端口、监控 Worker 状态、处理平滑重启(Reload)、Worker 异常崩溃时自动拉起新进程内部独立运行一个 asyncio 事件循环,接收网络事件、解析 HTTP 报文、执行 FastAPI 路由
内存关系进程间内存完全隔离,不共享任何全局变量每个 Worker 拥有自己独立的数据库连接池、Redis 连接与内存变量

生产 Worker 数量核算公式

  • 推荐公式:Workers = (2 * CPU 核心数) + 1
  • 容器化(Docker / K8s)环境例外:
    • 在云原生 K8s 环境下,更推荐单 Pod 单 Worker(只用 uvicorn 单进程跑),将多核扩展交给 K8s 的 HPA(Pod 自动横向扩缩容)。这样可以让 K8s 更加精准地监控每个 Pod 的 CPU/内存指标,避免单个 Pod 内多进程争抢资源引发 cgroups 节流。

二、生命周期与优雅停机(Lifespan Context Manager)

FastAPI 弃用了老旧的 @app.on_event("startup") 和 @app.on_event("shutdown"),全面转向基于异步上下文管理器的 Lifespan 模式:

from contextlib import asynccontextmanager
from fastapi import FastAPI
from app.database import engine
from app.redis import redis_client

@asynccontextmanager
async def lifespan(app: FastAPI):
    # -------- 1. 服务启动阶段 (Startup) --------
    print("[Startup] 初始化数据库连接池与 Redis 客户端...")
    await engine.connect()
    await redis_client.ping()
    
    yield  # 服务正常运行,接收外界请求
    
    # -------- 2. 优雅停机阶段 (Graceful Shutdown) --------
    print("[Shutdown] 收到终止信号,开始清理资源...")
    # 释放数据库物理连接池,防止连接残留造成 MySQL 僵尸连接
    await engine.dispose()
    await redis_client.close()
    print("[Shutdown] 资源释放完毕,安全退出。")

app = FastAPI(lifespan=lifespan)
  • K8s 优雅退出配合:
    • K8s 在停止 Pod 时,会先向进程发送 SIGTERM 信号,等待 terminationGracePeriodSeconds(默认 30s)。
    • Uvicorn 收到 SIGTERM 后,会立即停止接收新的 HTTP 请求,但会等待正在处理中的在途请求执行完毕,随后执行 lifespan 的 yield 之后代码释放连接池,最后干净退出,实现真正零报错的无损发布。

三、生产血泪坑:异步数据库连接池打满排查

在使用 SQLAlchemy (asyncio) 或 Tortoise-ORM 时,高并发下最常见的报错: TimeoutError: QueuePool limit of size 5 overflow 10 reached, connection timed out.

1. 产生根因

  1. 连接泄漏(Connection Leak):在路由中手动获取了 DB Connection,但没有放在 async with 块中,发生异常时连接未归还物理池。
  2. 长耗时计算阻塞连接:在持有数据库事务的期间,调用了耗时的第三方外部 HTTP/RPC 接口,导致数据库连接迟迟无法被其他请求复用。
  3. 连接池容量与 Worker 数量未对齐:
    • 假设后端启动了 8 个 Worker 进程,每个 Worker 默认配了 pool_size=20, max_overflow=10。
    • 总连接数最大可能飙升到:8 * 30 = 240 条物理连接!如果 MySQL max_connections 只有 151,瞬间把数据库打崩。

2. 生产优化配置

engine = create_async_engine(
    DATABASE_URL,
    pool_size=10,          # 常驻核心连接数
    max_overflow=20,       # 允许短时突发扩容的连接数
    pool_timeout=30,       # 排队等待连接的最大秒数
    pool_recycle=1800,     # 30分钟自动回收连接,防止 MySQL wait_timeout 强行切断连接引发“MySQL server has gone away”
    pool_pre_ping=True,    # 每次借出连接前发送 PING,确认连接依然存活
)

四、SSE 流式响应与 AI 打字机输出

在大模型对话应用中,使用 StreamingResponse 实现服务端向客户端的单向流式推送(Server-Sent Events):

import asyncio
from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()

async def fake_llm_stream():
    words = ["你", "好", "!", "我", "是", "高", "性", "能", "AI", "助", "手", "。"]
    for word in words:
        yield f"data: {word}\n\n"
        await asyncio.sleep(0.05)  # 模拟 LLM 吐词延迟

@app.get("/chat/stream")
async def chat_stream():
    return StreamingResponse(
        fake_llm_stream(),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Accel-Buffering": "no",  # ★ 关键:禁用 Nginx 反代缓冲,否则打字机会被 Nginx 攒满 4KB 才一次性喷出!
        }
    )
  • 生产避坑要点:
    • 如果前端通过 Nginx 反向代理访问 FastAPI,必须在响应头中加上 X-Accel-Buffering: no,否则 Nginx 会默认开启 proxy_buffering,把流式输出在内存中攒够几十个字符才发给前端,导致页面打字机变成“卡顿一阵突然喷出一大坨”。

面试高频追问

  1. Gunicorn 和 Uvicorn 分别扮演什么角色? Gunicorn 是进程管理器(Master-Worker 架构),负责管理进程生命周期、故障拉起与多核绑定;Uvicorn 是真正的异步 ASGI 服务器实现,在单个 Worker 内跑 asyncio 事件循环处理请求。
  2. K8s 中部署 FastAPI,推荐开多个 Worker 还是一个? 推荐单个 Pod 只开 1 个 Uvicorn Worker。将多核横向扩展交给 K8s 的 HPA(Pod 自动弹性伸缩),指标采集更精准,防止 Pod 内部多进程争抢触发 cgroups CPU 限速。
  3. FastAPI 如何保证服务平滑重启不丢在途请求? 使用现代 lifespan 上下文管理器配合 K8s SIGTERM 信号。服务停止新请求进入,等待在途请求执行完毕后,执行 yield 后的代码释放数据库和 Redis 连接池再退出。
  4. SSE 流式传输过 Nginx 时为什么页面打字机效果失效了? Nginx 默认开启了反向代理缓冲区(proxy_buffering on),会等待数据攒够一定大小(如 4KB)才发给前端。必须在响应头添加 X-Accel-Buffering: no 或在 Nginx 配置中关闭该路径的代理缓冲。
Navigation

Type to search…

↑↓ navigate↵ selectEsc close