FastAPI 教程

Chapter 10 · 性能优化与部署:走向世界

📚 学完本章,你将能够:

  • 用 Redis 缓存热点数据,减少数据库查询
  • 给 API 添加限流,防止被突发流量击垮
  • 用 Gunicorn + Uvicorn 部署生产环境
  • 编写 Dockerfile 多阶段构建,减小镜像体积
  • 配置 Nginx 反向代理、HTTPS 和 WebSocket 转发

💡 场景引入

你的图书 API 在小范围测试中表现良好。但上线第一天,首页书籍列表接口就被爬虫疯狂请求,数据库连接池耗尽,服务崩溃。

问题有三个:热门数据没缓存,每次请求都查数据库;没有限流,任何人可以无限请求;只跑了单进程,无法利用多核 CPU。本章解决这些问题,让你的服务能扛住真实流量。

10.1 缓存:把常用数据放内存

数据库查询通常是 API 最慢的一环。如果"热门书籍列表"每秒被请求 1000 次,每次都查数据库,数据库很快就会不堪重负。

缓存的思路是:第一次从数据库查出结果后,把它存到 Redis(内存数据库)里,后续请求直接读 Redis,速度快了几十倍。

Redis 缓存
import redis.asyncio as redis
from functools import wraps

r = redis.from_url("redis://localhost:6379")

async def get_books_cached():
    # 先查缓存
    cached = await r.get("books:popular")
    if cached:
        return json.loads(cached)

    # 缓存未命中,查数据库
    books = await fetch_books_from_db()

    # 写入缓存,设置 60 秒过期
    await r.setex("books:popular", 60, json.dumps(books))
    return books
缓存一致性

数据更新后,缓存里的旧数据不会自动消失。写操作后必须主动删除缓存(await r.delete("books:popular")),否则用户会看到过期内容。

10.2 限流:防止被请求淹没

登录接口如果被暴力破解,每秒几千次请求,数据库会扛不住。slowapi 提供简单的限流装饰器:

限流
from slowapi import Limiter
from slowapi.util import get_remote_address

limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter

@app.post("/auth/token")
@limiter.limit("5/minute")
async def login(...):
    ...

每个 IP 地址每分钟只能尝试 5 次登录。超限返回 429 Too Many Requests。

10.3 生产部署:Gunicorn + Uvicorn

开发时用 uvicorn main:app --reload,但生产环境需要多个进程同时处理请求,利用多核 CPU。

生产启动
gunicorn main:app \
  -k uvicorn.workers.UvicornWorker \
  -w 4 \
  -b 0.0.0.0:8000 \
  --access-logfile - \
  --error-logfile -
参数含义
-k uvicorn.workers.UvicornWorker使用 Uvicorn 作为 worker,支持 async
-w 4开 4 个 worker 进程,通常设为 (2 × CPU核数) + 1
--max-requests 10000每个 worker 处理 1 万请求后自动重启,防内存泄漏

10.4 Docker:打包你的应用

Docker 让你的应用带着所有依赖一起部署,避免"在我电脑上能跑"的问题。

Dockerfile
# 构建阶段
FROM python:3.11-slim as builder
WORKDIR /app
RUN pip install poetry
COPY pyproject.toml poetry.lock ./
RUN poetry install --no-dev --no-interaction

# 运行阶段(只保留必要文件,镜像更小)
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /app/.venv /app/.venv
COPY . .
ENV PATH="/app/.venv/bin:$PATH"
CMD ["gunicorn", "main:app", "-k", "uvicorn.workers.UvicornWorker", "-w", "4", "-b", "0.0.0.0:8000"]

10.5 Nginx:守门的大将

Nginx 放在 Gunicorn 前面,负责:SSL 终止(HTTPS)、静态文件、压缩、反向代理、负载均衡。

nginx.conf
server {
    listen 443 ssl http2;
    server_name api.example.com;

    ssl_certificate /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/key.pem;

    location / {
        proxy_pass http://app:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;

        # WebSocket 支持
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

🎯 知识检测

问题 1
缓存数据更新后,为什么要主动删除缓存而不是等它过期?
因为 Redis 不支持过期时间
防止用户在缓存过期前看到旧数据
删除缓存比设置过期更快
这是法律要求
解析:如果缓存 TTL 是 60 秒,数据更新后用户可能在 60 秒内一直看到旧内容。主动删除(Cache-Aside 模式)保证数据一致性。
问题 2
为什么生产环境用 Gunicorn + Uvicorn,而不是直接 uvicorn --reload?
因为 uvicorn 不支持 async
Gunicorn 管理多个 worker 进程,利用多核 CPU,且 --reload 有性能开销
因为 Gunicorn 是免费的
因为 uvicorn 有安全漏洞
解析:--reload 会监控文件变化并重启,有额外开销。生产环境需要多进程处理并发,Gunicorn 是成熟的进程管理器,UvicornWorker 让每个进程都能处理 async 请求。
问题 3
Nginx 在架构中的主要作用不包括?
SSL 终止(HTTPS)
反向代理到 Gunicorn
编译 Python 代码
WebSocket 转发
解析:Nginx 是 Web 服务器/反向代理,不负责编译代码。它处理 HTTPS、静态文件、压缩、负载均衡和 WebSocket 转发。

本章小结

✓

缓存减少数据库压力

Redis 存热点数据,但更新后主动删除保证一致性。

✓

限流保护关键接口

登录、注册等接口必须限流,防止暴力破解和突发流量。

✓

Gunicorn 管理多进程

worker 数按 CPU 核心配置,max-requests 防内存泄漏。

✓

Docker + Nginx 是生产标配

容器化部署保证环境一致,Nginx 处理 HTTPS 和静态资源。

🛠️ 动手实验

为你的图书 API 添加 Redis 缓存(缓存热门书籍列表 60 秒)。用 Docker Compose 启动 PostgreSQL + Redis + FastAPI 服务。
编写 Dockerfile 多阶段构建,配置 Nginx 反向代理(自签名 HTTPS + WebSocket 转发)。用 locust 做压力测试,对比加缓存前后的响应时间和数据库负载。