FastAPI入门:别再用Flask裸奔了,性能真的扛不住

萧敏☆
2025-12-27 22:31
阅读 1750

去年十月,我从某大厂“优化”了出来。说白了就是被毕业了——双11前一周,HR约我喝咖啡,聊完我就抱着显示器站在陆家嘴的秋风里发呆。Gap这半年,没躺平,也没刷LeetCode到秃头,而是把之前在项目里一直想试但被PM追着改需求没空碰的新技术,挨个撸了一遍。FastAPI 就是其中之一。

为啥选它?因为在大厂那会儿,我们一个内部工具链 API 用 Flask 写的,QPS 超过 300 就开始抖,加机器都救不回来。运维老哥半夜三点打电话骂我:“你这接口是不是又在循环查数据库?Go 都跑得比你快!”
我当时真想回一句:“要不你来写?”
但转念一想,他说得对——Python Web 框架确实该升级了。

于是,在出租屋里(上海静安寺附近,月租八千五,肉疼),我泡了三天速溶咖啡,把 FastAPI 从 Hello World 搞到了能扛压测的水平。今天这篇,不讲官方文档复读机内容,就聊聊一个刚裸辞、正焦虑找工作的前大厂人,怎么用 FastAPI 把性能搞上去,顺便给想入坑后端的同学指条明路。


为什么是 FastAPI?不是 Go,也不是 Django?

先说结论:如果你是 Python 开发,要做高性能 API 服务,又不想直接切到 Go 的陡峭学习曲线,FastAPI 是目前最平衡的选择。

我知道有人又要杠:“Go 多快啊!协程、编译型、静态链接,吊打 Python。”
没错,Go 在系统级性能上确实碾压。但现实是:团队里十个 Python 工程师,九个不会 Go。强行切换语言,沟通成本、招聘成本、上线风险全拉满。而 FastAPI 借助 Pydantic + Starlette + asyncio,在保持 Python 开发体验的同时,把性能提到了接近 Go 的 60%-70%(实测数据后面放)。

更关键的是——它自带 OpenAPI 和自动文档。上次我们组新来的实习生,光看 /docs 页面就把前端对接搞定了,连 PM 都夸“这次接口写得像人话”。


快速上手?别急,先看架构设计

很多人一上来就 pip install fastapi,然后写个 @app.get("/"),跑起来觉得“哇好快”,结果一上生产就崩。为什么?因为没考虑综合性能瓶颈

FastAPI 快,主要快在两个地方:

  1. 异步非阻塞 I/O(靠 Starlette 底层)
  2. 数据验证和序列化(靠 Pydantic,用 C 扩展加速)

但如果你在视图函数里写了个 time.sleep(5),或者循环查数据库十次,那再快的框架也救不了你。所以,算法和架构思维才是核心

举个真实例子:我们之前有个“用户行为分析”接口,逻辑是——

  • 传入 user_id
  • 查 MySQL 用户基础信息
  • 查 Redis 最近点击记录(100 条)
  • 调另一个微服务拿推荐列表
  • 合并数据返回

用 Flask 写的时候,同步调用,每个请求至少 800ms。后来我用 FastAPI 改造成 异步并发调用

from fastapi import FastAPI
import httpx
import aioredis
import asyncio

app = FastAPI()

@app.get("/user/profile/{user_id}")
async def get_user_profile(user_id: int):
    async with httpx.AsyncClient() as client:
        # 并发发起多个异步请求
        db_task = fetch_user_from_db(user_id)
        redis_task = fetch_clicks_from_redis(user_id)
        rec_task = client.get(f"http://rec-service/user/{user_id}")

        user, clicks, rec_resp = await asyncio.gather(
            db_task, redis_task, rec_task
        )

    return {
        "user": user,
        "clicks": clicks,
        "recommendations": rec_resp.json()
    }

注意:这里 fetch_user_from_db 也必须是异步的(比如用 aiomysqlSQLAlchemy 1.4+ async)。否则,整个 async 就形同虚设。

改完之后,P99 延迟从 1.2s 降到 320ms,QPS 从 280 提升到 1100+。运维老哥默默给我点了杯瑞幸。


性能对比:FastAPI vs Flask vs Go

为了验证效果,我在本地(MacBook Pro M1, 16GB)做了简单压测。测试接口:返回 {"hello": "world"}

框架 并发 100 QPS P99 延迟
Flask (sync) 100 ~420 240ms
FastAPI (async) 100 ~2100 48ms
Go (Gin) 100 ~8500 12ms

测试工具:wrk -t4 -c100 -d30s http://localhost:8000/

可以看到,FastAPI 虽然比不上 Go,但相比传统 Flask,性能提升 5 倍以上。而且代码复杂度几乎没增加。

但重点来了:实际业务中,瓶颈往往不在框架本身,而在数据库、缓存、网络 IO。所以别迷信“换框架就能起飞”,得做综合优化。


生产环境踩过的坑(血泪经验)

1. 别乱开 debug=True

上线第一天,我把本地配置原样复制到服务器,结果日志里全是 SQL 查询语句。更可怕的是,FastAPI 的自动 reload 功能在 debug 模式下会监听文件变化——生产环境 CPU 直接飙到 90%。

解决方案:严格区分环境变量

# main.py
import os
from fastapi import FastAPI

app = FastAPI(
    debug=os.getenv("DEBUG", "false").lower() == "true",
    docs_url="/docs" if os.getenv("ENV") != "prod" else None,
    redoc_url=None,
)

线上关掉文档,安全又省资源。


2. 异步上下文管理器别乱用

有次我用 async with some_client() 包裹一个长耗时操作,结果连接池被占满,新请求直接 503。原因:异步客户端默认连接池大小有限(比如 httpx 是 10)

解决方法:显式配置连接池

limits = httpx.Limits(max_connections=100, max_keepalive_connections=20)
client = httpx.AsyncClient(limits=limits, timeout=10.0)

或者,更好的做法是:全局复用一个 AsyncClient 实例(配合 lifespan 事件)。


3. Pydantic 模型别嵌套太深

Pydantic 快,是因为底层用了 C。但如果模型嵌套三层以上,带 validator,再配上大量字段,默认验证会变慢。

建议:

  • 复杂校验放到业务逻辑层,别全塞进 Pydantic
  • @validator 时注意性能,避免在 validator 里查数据库
  • 对高频接口,可考虑关闭部分验证(validate_assignment=False

4. 静态文件别用 Uvicorn 直接 serve

FastAPI 内置的静态文件路由 (app.mount("/static", StaticFiles(...))) 仅适合开发。生产环境请用 Nginx。

否则,每次请求静态资源都要经过 Python 进程,白白浪费 CPU。


综合性能优化 checklist

作为一个被线上事故虐过的人,我整理了一份 FastAPI 服务上线前必查项:

  • ✅ 使用 uvicorn 启动时加 --workers(根据 CPU 核数设,一般 2 * cores + 1
  • ✅ 数据库连接池配置合理(SQLAlchemy / asyncpg)
  • ✅ 所有外部调用(HTTP、DB、Redis)必须异步
  • ✅ 敏感接口加限流(可用 slowapi 或中间件)
  • ✅ 日志结构化(用 structlog),方便 ELK 分析
  • ✅ 健康检查接口 /healthz 必须有
  • ✅ Dockerfile 多阶段构建,减小镜像体积
  • ✅ Prometheus metrics 接入(用 fastapi-prometheus

算法?其实也在后端!

很多人以为算法只是 LeetCode 刷题,但在后端,算法思维直接影响性能

比如我们有个“实时排行榜”需求,最初用 MySQL ORDER BY score LIMIT 100,用户一多就慢成狗。后来改成 Redis 的 ZSET,查询 O(log N),瞬间丝滑。

FastAPI 里集成 Redis 很简单:

from redis.asyncio import Redis

redis = Redis(host="localhost", port=6379, decode_responses=True)

@app.get("/leaderboard")
async def get_leaderboard():
    top_users = await redis.zrevrange("scores", 0, 99, withscores=True)
    return [{"user_id": u, "score": s} for u, s in top_users]

你看,选对数据结构,比优化框架重要十倍。这也是为什么我一直觉得:后端工程师不能只会 CRUD,得懂点算法和系统设计。


结语:Gap 期不是浪费,是技术复利

现在我重新找工作,面试官问我:“裸辞半年干嘛了?”
我说:“研究 FastAPI,还顺手把以前项目的性能问题复盘了一遍。”

他们眼睛一亮——因为很多公司正在从 Flask/Django 迁移到 FastAPI,但没人敢动,怕出事。而我,已经在线下跑了三个月压测,还写了完整的监控方案。

技术人的 Gap 期,不该是焦虑和内耗,而是把以前欠下的技术债,一笔一笔还清

FastAPI 不是银弹,但它是一个信号:Python 后端正在进化。我们不必立刻切 Go,但必须拥抱异步、拥抱类型、拥抱性能意识。

最后吐槽一句:上周投了个岗位,JD 写“精通 FastAPI”,结果面完发现他们还在用 Python 2.7……算了,这种公司不去也罢。

如果你也在学 FastAPI,或者正被性能问题折磨,欢迎留言交流。我 VSCode 里装了一堆插件,连 .env 自动提示都有,说不定能帮上忙。

本文所有代码已整理到 GitHub,搜索 “fastapi-performance-playbook” 即可。别 star,star 了我也收不到钱 😅

评论 0

最热最新
暂无评论
萧敏☆Lv.1
0
影响力
0
文章
0
粉丝