FastAPI性能优化实战:从每秒100请求到1000的踩坑记录

一个9年后端老兵的实战笔记 · 真实项目经验分享

嘿,兄弟们!今天不聊那些虚的架构理论,咱们来点实在的:
分享一个真实的FastAPI性能优化案例。从每秒只能处理100个请求,到最终稳定支持1000+ QPS,这中间踩过的坑、流过的泪,都在这篇笔记里了。

项目背景:一个"看起来简单"的API服务

去年我们团队接了一个需求:为电商平台搭建一个商品推荐API。听起来挺简单对吧?接收用户ID,返回个性化商品列表。

技术栈选择:

FastAPI(当时最新版)

Pydantic(数据验证)

Redis(缓存用户行为)

PostgreSQL(存储商品信息)

Docker + Kubernetes(部署)

开发阶段一切顺利,2周搞定。测试环境跑得也挺欢。问题出现在压测阶段。

第一轮压测:残酷的现实

我们用Locust模拟了100个并发用户,持续请求推荐接口。结果让人心凉:

复制代码
Requests/sec: 98.3
95%响应时间: 1200ms
错误率: 0%

连100 QPS都没到! 这要是上线,大促期间肯定崩。

问题分析

通过 profiling 工具(pyinstrument + async-profiler),我们发现几个明显瓶颈:

数据库连接管理混乱:每个请求都创建新连接,连接池配置不当

同步阻塞操作:在async函数里调用了同步的Redis操作

不必要的序列化/反序列化:Pydantic模型配置不合理

缺少缓存策略:重复计算严重

第二轮优化:逐个击破

1. 数据库连接池优化

踩坑前代码:

python 复制代码
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

# 每个请求都这么干
def get_db():
    engine = create_engine(DATABASE_URL)
    SessionLocal = sessionmaker(engine)
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

问题:每次请求都创建新引擎和会话工厂,开销巨大。

优化后:

python 复制代码
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import QueuePool

# 全局单例引擎
engine = create_engine(
    DATABASE_URL,
    poolclass=QueuePool,
    pool_size=20,  # 连接池大小
    max_overflow=10,  # 允许超出的连接数
    pool_pre_ping=True,  # 连接前ping检查
    pool_recycle=3600,  # 1小时回收连接
)

SessionLocal = sessionmaker(bind=engine, expire_on_commit=False)

# 依赖注入
async def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

效果:QPS从98提升到180。连接复用真的很重要!

2. Redis异步客户端改造

踩坑前:

python 复制代码
import redis

redis_client = redis.Redis(host='localhost', port=6379)

# 在async函数中使用
async def get_user_history(user_id: str):
    # 这是同步调用,会阻塞事件循环!
    history = redis_client.get(f"user:{user_id}:history")
    return json.loads(history) if history else []

问题:同步Redis客户端在async环境中会阻塞整个事件循环。

优化方案:使用aioredis或redis-py的异步支持。

优化后:

python 复制代码
from redis.asyncio import Redis

# 全局异步客户端
redis_client = Redis(host='localhost', port=6379, decode_responses=True)

async def get_user_history(user_id: str):
    # 真正的异步操作
    history = await redis_client.get(f"user:{user_id}:history")
    return json.loads(history) if history else []
    
# 小技巧:别忘了配置连接池:
redis_client = Redis(
    host='localhost',
    port=6379,
    max_connections=50,
    socket_keepalive=True,
    retry_on_timeout=True,
)

效果:QPS从180提升到320。异步化改造带来的提升最明显。

3. Pydantic模型性能调优

踩坑前:

python 复制代码
from pydantic import BaseModel
from typing import List, Optional

class Product(BaseModel):
    id: int
    name: str
    price: float
    description: Optional[str] = None
    tags: List[str] = []
    # ... 还有20多个字段
    
class RecommendationResponse(BaseModel):
    user_id: str
    products: List[Product]
    recommendation_id: str
    timestamp: datetime
    metadata: Dict[str, Any] = {}

问题:
字段太多,序列化开销大
大量Optional字段增加了验证成本
嵌套层级深

优化策略:

按需返回:前端不需要的字段就别返回

使用__slots__:减少内存占用

优化验证逻辑:关闭不必要的严格验证

优化后:

python 复制代码
from pydantic import BaseModel, ConfigDict
from typing import List

class ProductSimple(BaseModel):
    """只包含必要字段的轻量级模型"""
    model_config = ConfigDict(
        from_attributes=True,  # 支持ORM对象转换
        extra='ignore',  # 忽略多余字段
        validate_assignment=False,  # 关闭赋值验证
    )
    
    id: int
    name: str
    price: float
    # 只保留核心字段
    
    __slots__ = ('id', 'name', 'price')  # 固定属性,减少内存

class RecommendationResponse(BaseModel):
    model_config = ConfigDict(
        json_encoders={
            datetime: lambda v: v.isoformat()  # 自定义日期序列化
        }
    )
    
    products: List[ProductSimple]
    recommendation_id: str
    # 去掉不必要字段

效果:QPS从320提升到450。模型精简对性能影响很大。

4. 缓存策略升级

第一版缓存:只缓存最终推荐结果

python 复制代码
async def get_recommendations(user_id: str):
    cache_key = f"rec:{user_id}"
    cached = await redis_client.get(cache_key)
    if cached:
        return json.loads(cached)
    
    # 复杂的计算逻辑(500ms)
    result = await compute_recommendations(user_id)
    
    # 缓存5分钟
    await redis_client.setex(cache_key, 300, json.dumps(result))
    return result

问题:用户行为变化时,缓存就会失效,导致重新计算。

优化方案:分层缓存 + 增量更新

优化后:

python 复制代码
# 第一层:用户特征缓存(更新频繁)
async def get_user_features(user_id: str):
    features_key = f"features:{user_id}"
    features = await redis_client.get(features_key)
    if not features:
        features = await compute_user_features(user_id)
        await redis_client.setex(features_key, 60, json.dumps(features))
    return json.loads(features)

# 第二层:商品特征缓存(很少变化)
async def get_product_features():
    products_key = "products:features"
    products = await redis_client.get(products_key)
    if not products:
        products = await compute_product_features()
        await redis_client.setex(products_key, 3600, json.dumps(products))
    return json.loads(products)

# 第三层:推荐算法(纯内存计算)
async def get_recommendations(user_id: str):
    user_features = await get_user_features(user_id)
    product_features = await get_product_features()
    
    # 实时计算(现在只要50ms)
    return await realtime_recommendation(user_features, product_features)

效果:QPS从450直接飙到850!缓存设计是性能优化的核武器。

第三轮:高级优化技巧

1. 使用uvloop加速事件循环

python 复制代码
import asyncio
import uvloop

asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

效果:QPS从850提升到920。uvloop比原生asyncio快约20%。

2. JIT编译热点函数

对于纯Python的计算密集型函数,可以用numba或pypy的JIT:

python 复制代码
# 假设有个评分计算函数很耗时
def calculate_product_score(user_features, product_features):
    # 复杂计算逻辑
    pass

# 用numba加速
from numba import jit

@jit(nopython=True)
def calculate_product_score_fast(user_features, product_features):
    # 重写为numba友好版本
    pass

注意:numba对Python语法有限制,需要调整代码。

3. 使用更快的JSON库

FastAPI默认用json模块,换成orjson或ujson:

python 复制代码
from fastapi.responses import ORJSONResponse

app = FastAPI(default_response_class=ORJSONResponse)

效果:序列化速度快2-3倍。

最终成果与监控

经过三轮优化,我们的API表现:

python 复制代码
最终压测结果:
Requests/sec: 1024.7
95%响应时间: 95ms
错误率: 0%
内存使用: 稳定在1.2GB
CPU使用率: 65-70%
监控指标:
应用性能监控(APM):New Relic
日志聚合:ELK Stack
指标收集:Prometheus + Grafana
告警:PagerDuty

经验总结与反思

做得好的地方:

问题定位准确:profiling工具用得及时,没有盲目优化

渐进式优化:一轮一轮来,每轮都有明确目标

监控先行:优化前建立了基准,优化后有数据对比

团队协作:前后端一起讨论接口设计,避免过度设计

踩过的坑(希望你别再踩):

过早优化:最开始想用微服务拆分,结果发现单体性能都没搞定

技术选型盲从:看到别人用GraphQL就跟风,结果复杂度暴增

忽视运维成本:用了很多"高级"特性,部署和调试都很痛苦

给Python后端开发者的建议:

先测量,再优化:不要凭感觉,一定要有数据支撑

保持简单:能用简单方案就别用复杂的

关注可观测性:代码要容易调试和监控

持续学习:FastAPI生态发展很快,新特性可能解决老问题

最后的话

性能优化不是一次性的任务,而是一个持续的过程。我们的服务上线后,仍然在持续监控和优化:

每周review性能指标

每月分析慢查询和瓶颈

每季度做一次架构评估

作为9年经验的后端,我的体会是:技术深度很重要,但工程思维更重要。知道什么时候该优化、优化到什么程度、用什么代价优化,这些判断往往比写代码本身更难。

你在性能优化方面有什么经验或问题?欢迎在评论区交流讨论。如果觉得这篇分享有用,别忘了点赞收藏,下次我们聊聊"微服务中的分布式缓存一致性问题"。

本文基于真实项目经验整理,代码已脱敏处理。不同业务场景可能需要不同的优化策略,请结合实际情况进行调整。

相关推荐
知我Deja_Vu2 小时前
【避坑指南】ConcurrentHashMap 并发计数优化实战
java·开发语言·python
njidf2 小时前
用Python制作一个文字冒险游戏
jvm·数据库·python
呆呆小孩2 小时前
Anaconda 被误删抢救手册:从绝望到重生
python·conda
liliangcsdn2 小时前
LLM复杂数值的提取计算场景示例
人工智能·python
人工智能AI酱2 小时前
【AI深究】逻辑回归(Logistic Regression)全网最详细全流程详解与案例(附大量Python代码演示)| 数学原理、案例流程、代码演示及结果解读 | 决策边界、正则化、优缺点及工程建议
人工智能·python·算法·机器学习·ai·逻辑回归·正则化
WangLanguager3 小时前
逻辑回归(Logistic Regression)的详细介绍及Python代码示例
python·算法·逻辑回归
wefly20173 小时前
m3u8live.cn 在线M3U8播放器,免安装高效验流排错
前端·后端·python·音视频·前端开发工具
ZTLJQ3 小时前
深入理解逻辑回归:从数学原理到实战应用
开发语言·python·机器学习
fundoit3 小时前
WSL存储性能优化完全指南:从原理到实践
性能优化