FastAPI上线实战:认证、限流、零停机,一套代码搞定
版权声明
我们非常重视原创文章,为尊重知识产权并避免潜在的版权问题,我们在此提供文章的摘要供您初步了解。如果您想要查阅更为详尽的内容,访问作者的公众号页面获取完整文章。
昨天还在为开发效率沾沾自喜,今天就在为生产环境焦头烂额。FastAPI 让你 5 分钟写出 Hello World,但真正让它扛住真实流量,完全是另一回事。
这篇文章来自一线实战,包含可直接复用的代码和经过验证的部署策略。读完你会得到一套完整的“生产环境生存指南”:谁可以进来(认证)、能做多少事(限流)、以及如何在不中断服务的情况下升级系统(零停机部署)。
核心概念类比:就像开一家餐厅
想象你开了一家很火的餐厅:
认证 = 门口保安,只让有会员卡的客人进来 限流 = 厨房产能有限,高峰期只能一桌一桌上菜,不能把所有客人都塞进来 零停机部署 = 餐厅在营业时间更换菜单,但客人完全感觉不到,菜还在继续上
下面我们就按这三个维度,一步步搭起来。
1. 认证:让每一次请求都有“身份证”
JWT(JSON Web Token)是当下最主流的认证方案。但生产环境需要特别注意几个细节。
为什么不能只用 JWT 字符串?
很多新手会犯一个错误:直接在代码里写死一个 JWT 密钥,验证通过就行。这相当于给餐厅配了个只认一张会员卡的保安——哪天换卡了(密钥轮换),所有客人都被拒之门外。
正确的做法是使用 OIDC(OpenID Connect)标准,让服务从认证服务器的 JWKS 端点动态获取公钥。
核心依赖:JWKS 自动更新
# auth.py from fastapi import Depends, HTTPException, status, Request from jose import jwt from httpx import AsyncClient import time import asyncio # 缓存 JWKS 公钥,避免每次请求都去拉取 JWKS_CACHE = {"keys": [], "exp": 0} ISSUER = "https://accounts.example.com"# 替换为你的认证服务 AUD = "api"# 预期的 audience ALGOS = ["RS256"] # 只允许 RS256 JWKS_URL = f"{ISSUER}/.well-known/jwks.json" asyncdef load_jwks(): """从认证服务器加载公钥,带缓存""" global JWKS_CACHE if time.time() < JWKS_CACHE["exp"]: return JWKS_CACHE["keys"] asyncwith AsyncClient(timeout=3) as c: resp = await c.get(JWKS_URL) resp.raise_for_status() data = resp.json() # 缓存 10 分钟,避免频繁拉取 JWKS_CACHE = {"keys": data["keys"], "exp": time.time() + 600} return JWKS_CACHE["keys"] asyncdef current_user(request: Request): """依赖注入:从请求中提取并验证当前用户""" auth = request.headers.get("authorization", "") ifnot auth.startswith("Bearer "): raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Missing or invalid token format") token = auth.split(" ", 1)[1] keys = await load_jwks() try: claims = jwt.decode( token, keys, algorithms=ALGOS, audience=AUD, issuer=ISSUER ) return { "sub": claims["sub"], "scopes": claims.get("scope", "").split() } except Exception as e: # ⚠️ 这里不要打印完整 token,只打印错误类型 raise HTTPException(status.HTTP_401_UNAUTHORIZED, f"Invalid token: {type(e).__name__}")
在路由中使用
from fastapi import FastAPI, Depends from auth import current_user app = FastAPI() @app.get("/me") async def me(user=Depends(current_user)): """获取当前用户信息""" return {"user": user["sub"]}
生产级认证检查清单
⚠️ 注意:90% 的人会在这里踩坑
| Token 有效期 | ||
| 密钥管理 | ||
| 吊销处理 | ||
| Cookie vs Header | ||
| 日志安全 |
2. 限流:给你的系统装上“减压阀”
没有限流的系统,就像没有刹车的车。一个恶意用户或一个突发流量就能把整个服务拖垮。
为什么选择 Redis + Token Bucket?
Token Bucket(令牌桶)算法最适合 API 场景:
允许短时突发流量(burst) 长期平均速率可控 分布式环境下用 Redis 保证原子性
类比:就像地铁闸机
每个乘客刷卡需要消耗 1 个令牌 闸机每秒自动补充固定数量的令牌(如 50 个/秒) 高峰时段令牌消耗快,但桶里还有存货(允许突发) 令牌用完 → 闸机关闭,提示“请稍后再试”
基于 Redis Lua 的原子性限流
100, refill_rate=50) return limiter asyncdef rate_limit(request: Request): """依赖注入:限流中间件""" limiter = await get_limiter() # 根据场景选择限流维度 # 认证用户用 user_id,匿名用 IP user_id = request.headers.get("x-user-id") if user_id: key = f"user:{user_id}" else: key = f"ip:{request.client.host}" allowed = await limiter.check(key) ifnot allowed: raise HTTPException( status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail="Rate limit exceeded. Please slow down.", headers={"Retry-After": "30"} # 告诉客户端 30 秒后重试 )
在路由中使用
@app.get("/search", dependencies=[Depends(rate_limit)]) async def search(q: str): """搜索接口,限制 50 请求/秒,突发 100""" # 业务逻辑... return {"results": []}
限流策略矩阵
3. 零停机部署:让你的服务永不掉线
“发版就重启”是开发环境的做法。生产环境里,一个重启可能丢掉几十个正在处理的请求。
核心概念:优雅关闭
优雅关闭的意思是:收到终止信号后,不再接收新请求,但继续处理完已有的请求,然后才退出。
Uvicorn + Lifespan 最佳实践
yield# 应用运行期间 # 关闭时执行(优雅关闭时会等待) await app.state.db_pool.close() await app.state.redis.close() app = FastAPI(lifespan=lifespan) @app.get("/health/live") asyncdef liveness(): """存活探针:进程是否还在""" return {"status": "alive"} @app.get("/health/ready") asyncdef readiness(): """就绪探针:依赖是否就绪""" try: # 检查数据库 asyncwith app.state.db_pool.acquire() as conn: await conn.execute("SELECT 1") # 检查 Redis await app.state.redis.ping() return {"status": "ready"} except Exception as e: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=f"Not ready: {e}" )
部署配置(Docker + Kubernetes)
# Dockerfile FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . # 多 worker 启动,优雅关闭超时 30 秒 CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", \ "--workers", "4", "--graceful-timeout", "30"]
# k8s-deployment.yaml apiVersion:apps/v1 kind:Deployment metadata: name:fastapi-app spec: replicas:3 strategy: type:RollingUpdate rollingUpdate: maxSurge:1 # 允许多启动 1 个 pod maxUnavailable:0# 不允许有 pod 不可用 template: spec: containers: -name:app image:fastapi:latest ports: -containerPort:8000 livenessProbe: httpGet: path:/health/live por
数据STUDIO
文章来源:
数据STUDIO
扫码关注公众号
点击领取《Python学习手册》,后台回复「福利」获取。『数据STUDIO』专注于数据科学原创文章分享,内容以 Python 为核心语言,涵盖机器学习、数据分析、可视化、MySQL等领域干货知识总结及实战项目。
还在用多套工具管项目?
一个平台搞定产品、项目、质量与效能,告别整合之苦,实现全流程闭环。
白皮书上线