你的 FastAPI 慢得离谱,但你可能不知道 FastAPI 本身快吗?确实快。但那只是框架层面的潜力
FastAPI 本身快吗?确实快。但那只是框架层面的潜力。如果直接用默认配置一把梭,就好比买了辆跑车,却天天挂着一挡开——这能怪车慢吗?
免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈
今天分享五个关键配置,无需改动核心业务逻辑,也不必更换架构,调整后性能提升立竿见影。

这是最简单、也是收益最显著的一步。
FastAPI 默认使用 Uvicorn 单进程启动,这意味着即便你拥有一台 8 核服务器,实际上也只用到了 1 个核,其余 7 个都在闲置。
# 错误示范:单进程
# uvicorn main:app
# 正确做法:多 worker
# uvicorn main:app --workers 4
或者使用配置文件进行更精细的控制:
# gunicorn_conf.py
import multiprocessing
workers = multiprocessing.cpu_count() * 2 + 1 # 推荐公式
# 但注意,worker数通常不超过 CPU 核数的 2-4 倍,过多反而会因进程切换拖慢速度
worker_class = "uvicorn.workers.UvicornWorker"
bind = "0.0.0.0:8000"
启动命令相应调整为:
gunicorn main:app -c gunicorn_conf.py
需要特别注意的是,在多 worker 模式下,内存中的变量是不共享的。因此,切勿使用全局字典来存储状态。如果确实需要共享状态,引入 Redis 这类外部存储是更稳妥的方案。
你是否是这样写的接口?
@app.get("/users/{user_id}")
async def get_user(user_id: int):
user = await fetch_user_from_db(user_id)
return user # 直接返回 ORM 对象
问题在于,ORM 对象身上往往挂载着大量私有属性、延迟加载的关联数据以及内部方法。JSON 序列化时,框架需要遍历处理所有这些内容,速度自然快不起来。
正确的做法是加上 response_model:
from pydantic import BaseModel
class UserResponse(BaseModel):
id: int
name: str
email: str
# 只定义需要暴露的字段
@app.get("/users/{user_id}", response_model=UserResponse)
async def get_user(user_id: int):
user = await fetch_user_from_db(user_id)
return user
这样一来,FastAPI 将只序列化 response_model 中明确定义的字段,自动跳过 ORM 对象上所有无关的属性。当返回数据量较大时,序列化速度提升 40% 到 60% 是常有的事。此外,这个做法还顺便堵住了敏感字段意外泄漏的风险,可谓一举两得。
FastAPI 的中间件遵循“洋葱模型”,后添加的中间件会先执行。但很多开发者忽略了顺序,随手添加:
# 错误示范:CORS 放在最外层,所有请求(包括静态资源)都要过一遍
app.add_middleware(GZipMiddleware) # 第3个添加
app.add_middleware(CORSMiddleware, ...) # 第2个添加
app.add_middleware(TimeLogMiddleware) # 第1个添加
# 实际执行顺序:TimeLog → CORS → GZip → 路由
问题出在哪里?CORS 中间件会对所有请求(包括无需跨域的静态资源)进行检查,GZip 中间件也可能尝试压缩已经压缩过的响应。这些不必要的操作都在白白消耗 CPU 资源。
正确的顺序原则是:将最轻量的中间件放在最外层,最耗时的放在最内层。
# 推荐顺序:从外到内
app.add_middleware(TimeLogMiddleware) # 日志记录,最轻量
app.add_middleware(GZipMiddleware) # 响应压缩,中等开销
app.add_middleware(CORSMiddleware, ...) # CORS 处理,相对最重
# 执行顺序:TimeLog → GZip → CORS → 路由
仅仅是这样一个小调整,每个请求节省几十毫秒轻而易举。如果日请求量达到百万级别,节省的总时间将非常可观。
这是新手最容易犯的错误,同时也是最容易修复的性能瓶颈。
# 错误示范:每次请求都新建连接
@app.get("/users")
async def list_users():
conn = await asyncpg.connect("postgresql://...")
users = await conn.fetch("SELECT * FROM users")
await conn.close()
return users
建立一次数据库连接的成本有多高?通常包括 TCP 三次握手、TLS 握手以及数据库鉴权,整个过程耗时可能在 50 到 100 毫秒之间。每个请求都重复这个过程,接口响应慢也就不足为奇了。
正确的做法是使用连接池:
from databases import Database
database = Database("postgresql://...", min_size=5, max_size=20)
@app.on_event("startup")
async def startup():
await database.connect()
@app.on_event("shutdown")
async def shutdown():
await database.disconnect()
@app.get("/users")
async def list_users():
users = await database.fetch_all("SELECT * FROM users")
return users
其中,min_size=5 表示应用启动时就预先建立好 5 个连接备用,max_size=20 则限定了连接池的最大容量。请求到来时直接从池中获取可用连接,用完后归还,完全避免了重复建立连接的开销。
如果使用 SQLAlchemy,原理相同:
from sqlalchemy.ext.asyncio import create_async_engine
engine = create_async_engine(
"postgresql+asyncpg://...",
pool_size=10, # 连接池常驻连接数
max_overflow=20, # 高峰期允许额外创建的连接数
pool_recycle=3600, # 连接使用1小时后回收,防止数据库端主动断开
pool_pre_ping=True, # 从池中取出连接前先执行简单检测,确保连接存活
)
这里特别提一下 pool_pre_ping=True 这个参数,它堪称“救星配置”。数据库通常设有空闲连接超时断开机制,若代码持有一个已被服务端关闭的“死连接”去执行查询,会直接导致错误。开启 pre_ping 后,ORM 会在每次使用前自动检查连接有效性,并在失效时重新连接,极大地提升了稳定性。
数据库查询再优化,其速度也难与内存读取相比。对于那些更新频率较低的数据,引入缓存是立竿见影的方案。
对于单机应用,可以使用 Python 内置的缓存:
from functools import lru_cache
@lru_cache(maxsize=128)
def get_config(key: str):
"""配置项基本不变,非常适合缓存"""
return db.query(Config).filter_by(key=key).first()
如果需要支持过期时间,可以考虑更专业的库:
from cachetools import TTLCache
cache = TTLCache(maxsize=100, ttl=300) # 缓存100条,每条存活5分钟
@app.get("/hot-articles")
async def hot_articles():
if "hot_articles" in cache:
return cache["hot_articles"]
articles = await fetch_hot_articles_from_db()
cache["hot_articles"] = articles
return articles
如果是分布式部署,单机内存缓存就不够用了,此时需要引入 Redis 这类分布式缓存:
import aioredis
import json
redis = aioredis.from_url("redis://localhost")
@app.get("/hot-articles")
async def hot_articles():
cached = await redis.get("hot_articles")
if cached:
return json.loads(cached)
articles = await fetch_hot_articles_from_db()
await redis.set("hot_articles", json.dumps(articles), ex=300)
return articles
当然,缓存并非银弹。在写入频繁、读取较少的场景下,缓存收益甚微;而对于数据一致性要求极高的场景,则需要谨慎设计缓存策略。用错了缓存,带来的问题可能比不用更严重。
以上五个配置,无需修改业务代码,也无需引入复杂的新架构,调整后通常能看到明显效果。如果按收益和优先级排序,大致如下:
千万别小看这些“配置细节”。很多时候接口性能不佳,问题并非出在框架本身,而恰恰在于这些基础配置没有被认真对待和优化。
菜鸟下载发布此文仅为传递信息,不代表菜鸟下载认同其观点或证实其描述。