FastAPI为什么成为默认答案

一个2018年才诞生的框架,凭什么用7年就超越统治了十几年的Django和Flask?答案藏在三个时代级的结构性变化里。


开场:一组让人沉默的数据

先别急着看"FastAPI是什么"。我们直接上数据。

根据JetBrains联合Python Software Foundation发布的2024年度Python开发者调查 ------覆盖近200个国家、超过3万名开发者------FastAPI的开发者使用率在一年内从29%跃升至38%,同比增长约31%,首次正式超越Django(35%)和Flask(34%),成为Python生态中使用率最高的Web框架。

GitHub上,FastAPI星标数已突破92k ,超过Flask(约71k)和Django(约86k)。超过一半的财富500强企业已在生产环境使用FastAPI,微软、Netflix、Uber都在列。而在AI领域,这个名单还要加上OpenAI、Anthropic、Cohere------也就是说,你每调用一次ChatGPT API或Claude API,背后大概率有FastAPI在处理请求。

这背后有一个关键拐点:2022年底ChatGPT发布。在那之前,FastAPI已经是增长很快的明星框架,但趋势还不算压倒性。AI浪潮来了之后,它的增长曲线直接从"快速上升"变成了"近乎垂直"。

一个2018年底才发布、满打满算才8岁的框架,凭什么在Django和Flask统治了十几年的Python生态里做到这件事?

这不是一个"FastAPI比别人更好"的简单故事。真正的答案是------它恰好踩中了这个时代三个结构性需求的交叉点:异步I/O从加分项变成标配、类型安全从可选变成必需、AI应用大规模爆发需要一个原生适配的API层。

接下来我们一个一个拆。


驱动力一:异步从"可选项"变成"标配"

要理解FastAPI的性能优势,得先搞懂它底下的ASGI协议到底解决了什么问题。

WSGI时代的天生缺陷

Django和Flask都是基于WSGI(Web Server Gateway Interface)协议的。WSGI是2003年制定的标准,在那个年代它非常优雅------一个请求进来,调用一个Python函数,返回一个响应,简单直接。

但它有一个致命的设计假设:每个请求都是同步阻塞的。什么意思?你的代码在等数据库返回、等外部API响应、等磁盘I/O的时候,整个处理线程就干等着,什么都做不了。

打个比方:WSGI模型就像一个餐厅只有一个服务员,他给第一桌点完菜之后,必须站在厨房门口等菜做好,才能去给第二桌点菜。厨房做菜那5分钟里,服务员完全空转。

这在2003年不算大问题------那时候的Web应用主要是"查数据库、渲染模板、返回HTML",I/O操作相对简单。但今天呢?一个AI应用的一次请求可能同时要做这些事:

  • 调用Embedding模型生成向量(耗时100-300ms)
  • 查向量数据库做相似度检索(耗时50-200ms)
  • 调用LLM推理生成结果(耗时1-10秒,甚至更长)
  • 流式返回token给前端
  • 可能还要查用户信息、写日志、调用其他微服务

如果用同步框架处理这类请求,服务器资源会大量浪费在"等"上。

ASGI的本质:一个服务员同时服务所有桌

ASGI(Asynchronous Server Gateway Interface)是2018年前后成型的新标准。它用一个叫做"事件循环"的机制,让单个线程可以在I/O等待期间切换去处理其他请求。

还是那个餐厅的比喻:ASGI模式下,服务员点完第一桌菜之后,不需要在厨房门口傻等,他可以直接去给第二桌、第三桌点菜。等厨房喊"菜好了",他再回来端菜。一个服务员就能同时服务很多桌。

这不是玄学,是实打实的性能差距:

上图综合了TechEmpower基准测试和AWS云实测数据(2025-2026年)。注意一个关键规律:I/O操作越多、等待时间越长,FastAPI对同步框架的优势越明显。纯JSON序列化场景FastAPI快约8倍;到了含LLM调用的场景,因为单次请求耗时本来就长,绝对QPS都会下降,但FastAPI的并发优势依然存在------而且这还没算上流式响应带来的体验提升。

从代码层面看,差异也非常直观。

同步写法(Flask风格)------请求一来,线程就被占死了:

less 复制代码
@app.route("/chat")
def chat():
    # 这行阻塞当前线程,其他请求只能排队等着
    response = requests.post("https://api.openai.com/v1/chat/completions", ...)
    return response.json()

**异步写法(FastAPI风格)**------等待期间自动让出执行权:

@app.post("/chat")
async def chat(req: ChatRequest):
    # await期间事件循环可以去处理其他请求
    async with httpx.AsyncClient() as client:
        response = await client.post(
            "https://api.openai.com/v1/chat/completions",
            json={"model": "gpt-4o", "messages": req.messages}
        )
    return response.json()

但是!异步 ≠ 自动高性能

这里必须讲一个非常重要的"反直觉"事实,也是新手最容易踩的坑:

如果你在async函数里调用了同步阻塞代码,事件循环会被整个卡住,异步的优势瞬间归零,甚至可能比同步还慢。

典型的错误写法:

python 复制代码
@app.get("/bad-example")
async def bad_example():
    # ❌ requests是同步库!这行会把整个事件循环堵死
    r = requests.get("https://api.example.com/data")
    # ❌ time.sleep也是同步阻塞
    time.sleep(1)
    # ❌ 同步ORM操作(SQLAlchemy同步Session
    user = db.query(User).filter(User.id == 1).first()
    return {"ok": True}

在这个反例里,你虽然写了async def,但里面全是阻塞调用,性能可能还不如直接用Flask------因为Flask至少还会用多线程来并行处理请求,而你把唯一的事件循环线程堵死了。

后面讲生产案例的时候,我们会看到一个真实踩坑的故事,非常典型。


驱动力二:类型安全从"给IDE看"变成"运行时约束"

Python的类型提示(Type Hints)从Python 3.5开始引入,但很长一段时间里,它的作用基本就是"给IDE补全用"、"给代码阅读者看"------运行时Python根本不会检查你传的类型对不对。

php 复制代码
def add(a: int, b: int) -> int:
    return a + b

add("hello", "world")  # IDE会标红,但运行起来完全不报错

这在写小脚本时没问题,但在写API接口时就是灾难:前端传了一个字符串类型的user_id给你,你以为是整数,查数据库时直接报错;或者客户端漏传了一个必填字段,你直到深入业务逻辑才发现。

传统的解决方案是什么?每个接口手写一堆if "field" not in data: raise ValueError(...),或者用Marshmallow、DRF Serializer之类的库单独写一套schema。代码重复、文档脱节、维护成本高,改一个字段要改三处。

Pydantic做了一件事:把类型注解变成运行时契约

FastAPI基于Pydantic,做了一件极其优雅的事:你写的Python类型注解,就是数据校验规则,就是API文档,就是序列化/反序列化逻辑。一处定义,处处生效。

python 复制代码
from fastapi import FastAPI
from pydantic import BaseModel, Field, field_validator
from datetime import datetime
from enum import Enum

app = FastAPI()

class Priority(str, Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"

class CreateTodoRequest(BaseModel):
    title: str = Field(..., min_length=1, max_length=200, description="待办标题")
    priority: Priority = Field(default=Priority.MEDIUM, description="优先级")
    due_date: datetime | None = Field(default=None, description="截止时间")
    tags: list[str] = Field(default_factory=list, description="标签列表")

    @field_validator("title")
    @classmethod
    def title_not_empty(cls, v: str) -> str:
        if v.strip() == "":
            raise ValueError("标题不能是纯空白")
        return v.strip()

class TodoResponse(BaseModel):
    id: int
    title: str
    priority: Priority
    created_at: datetime
    completed: bool = False

@app.post("/todos", response_model=TodoResponse, status_code=201)
async def create_todo(todo: CreateTodoRequest):
    # 到这里,todo已经被自动校验、自动类型转换
    # title一定是1-200个非空白字符
    # priority一定是low/medium/high之一
    # due_date如果传了就一定是合法datetime
    new_todo = await todo_service.create(todo)
    return new_todo

这段代码看起来平平无奇,但它同时完成了五件事:

  1. 自动解析请求体JSON:前端发来的JSON自动转成Python对象
  2. 自动类型校验:字段类型不对、长度超限、非法枚举值,通通返回结构化的422错误
  3. 自动类型转换 :前端传"2026-09-21T10:00:00Z"自动转成datetime对象
  4. 自动序列化返回值:返回的ORM对象自动转成JSON,datetime自动转ISO格式
  5. 自动生成OpenAPI文档 :打开/docs,Swagger UI已经有完整的接口文档,请求体示例、字段说明、响应格式一应俱全

Pydantic v2把核心校验逻辑用Rust重写了,性能比v1提升了5-50倍,这也是FastAPI在性能上能跟Go框架掰手腕的重要原因之一。

根据行业估算,类型校验和自动序列化能减少大约**40%**的常见API错误(漏字段、类型错、格式错)。更重要的是------文档永远跟代码同步。改了字段,文档自动更新,再也不会出现"文档是v1的,接口已经是v3的"这种惨剧。


驱动力三:AI时代的"原生适配"

如果说前两个驱动力------异步和类型安全------是"快和稳"的通用能力,那第三个驱动力才是FastAPI在2023年之后爆发式增长的真正加速器:它天然就是为AI应用设计的

AI应用的典型模式是什么?

一个现代AI应用,不管是RAG问答、Agent工具调用、还是多模态生成,它的后端请求流几乎都是这样的:

erlang 复制代码
接收用户请求
  ├─ 验证鉴权(DB查询)
  ├─ 生成query embedding(调用向量模型API
  ├─ 检索相关文档(向量数据库查询
  ├─ 可能调用多个工具(搜索API、代码执行、数据库...
  ├─ 构建Prompt调用LLM(流式响应可能持续几秒到几十秒)
  └─ 一边生成一边流式返回token给前端(SSE / WebSocket

你发现没有?几乎每一步都是I/O操作 ,而且最后一步特别关键------用户期望的不是"加载10秒然后一下子出结果",而是像ChatGPT那样一个字一个字蹦出来的流式体验

这正是FastAPI最擅长的场景。我们看一个真实可用的LLM流式接口:

python 复制代码
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from typing import AsyncGenerator
import httpx
import json

app = FastAPI()

async def stream_llm_response(prompt: str) -> AsyncGenerator[str, None]:
    """调用LLM并以SSE格式逐token流式返回"""
    async with httpx.AsyncClient(timeout=60.0) as client:
        async with client.stream(
            "POST",
            "https://api.openai.com/v1/chat/completions",
            headers={"Authorization": f"Bearer {API_KEY}"},
            json={
                "model": "gpt-4o-mini",
                "messages": [{"role": "user", "content": prompt}],
                "stream": True,  # 开启流式
            },
        ) as response:
            async for line in response.aiter_lines():
                if line.startswith("data: "):
                    data = line[6:]
                    if data == "[DONE]":
                        yield "data: [DONE]\n\n"
                        break
                    try:
                        chunk = json.loads(data)
                        token = chunk["choices"][0]["delta"].get("content", "")
                        if token:
                            # SSE格式:data: xxx\n\n
                           yield f"data: {json.dumps({'token': token})}\n\n"
                    except json.JSONDecodeError:
                       continue

@app.get("/chat/stream")
async def chat_stream(q: str):
    """流式聊天接口:前端通过EventSource接收"""
    return StreamingResponse(
        stream_llm_response(q),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "X-Accel-Buffering": "no",  # 禁止Nginx缓冲
        },
    )

前端只需要十几行代码就能接:

ini 复制代码
const es = new EventSource(`/chat/stream?q=${encodeURIComponent(question)}`);
es.onmessage = (e) => {
   if (e.data === "[DONE]") { es.close(); return; }
   const { token } = JSON.parse(e.data);
   answerEl.textContent += token;  // 逐字追加,体验跟ChatGPT一样
};

更进一步:异步并发多个I/O

更厉害的是,当你需要同时调用多个外部服务时,asyncio.gather可以让它们并行执行:

ini 复制代码
@app.post("/rag-qa")
async def rag_qa(question: str):
   # 三件事并行做,总耗时 ≈ 最慢的那一个
   embedding_task = embed_model.aembed(question)        # 生成向量
   user_task = user_service.get_current_user()          # 查用户信息
   history_task = chat_history.get_recent(limit=10)     # 查历史对话

   embedding, user, history = await asyncio.gather(
       embedding_task, user_task, history_task
   )

   # 然后并行查多个知识库
   docs = await asyncio.gather(
       vector_db.search(embedding, collection="faq"),
       vector_db.search(embedding, collection="manuals"),
       vector_db.search(embedding, collection="cases"),
       )
    all_docs = [d for col in docs for d in col]

   return await llm.generate(question, context=all_docs, history=history)

在同步框架里,这些调用是串行的------总耗时是所有步骤相加。用FastAPI的异步模式,总耗时约等于最慢的那一步。对于一个要调用3-5个外部服务的AI应用,这可能是2秒和10秒的差距。

正是因为这种"原生适合",OpenAI的API网关、Anthropic的Claude API、Cohere的推理服务、HuggingFace的推理端点,底层都大量使用FastAPI。在国内,字节跳动、阿里等公司的AI服务也在向FastAPI迁移。


验证:真实生产案例,以及一个5倍性能的坑

数据和代码都是"理论上的"。我们来看一个真实的生产案例,看看踩坑和填坑的过程。

Shippo的账单平台:42 RPS → 210 RPS的故事

Shippo是一家做物流API的公司(Y Combinator毕业,融资过亿美元),他们的账单平台需要每天处理数十万个webhook回调------典型的高并发I/O场景。团队选择了FastAPI来构建这个服务。

一开始,压测结果让他们大跌眼镜:吞吐量只有42 RPS,延迟高得离谱,完全没体现出FastAPI的异步优势。

问题出在哪?团队排查后发现,他们在async路由里调用了一个同步的AWS SQS发送函数(当时用的boto3客户端是同步版)。就这一行,把整个事件循环堵死了,所有请求串行排队。

python 复制代码
# ❌ 错误写法:async路由里直接调用同步boto3
@app.post("/webhook")
async def handle_webhook(payload: WebhookPayload):
    # 这一行是同步阻塞,整个事件循环卡住
   sqs_client.send_message(QueueUrl=QUEUE_URL, MessageBody=json.dumps(payload.dict()))
    await process_webhook(payload)
    return {"status": "ok"}

修复方式是用FastAPI/Starlette提供的run_in_threadpool,把同步调用扔到线程池里执行,不阻塞事件循环:

python 复制代码
from fastapi.concurrency import run_in_threadpool

# ✅ 正确写法:同步阻塞代码交给线程池
@app.post("/webhook")
async def handle_webhook(payload: WebhookPayload):
    await run_in_threadpool(
        sqs_client.send_message,
        QueueUrl=QUEUE_URL,
        MessageBody=json.dumps(payload.model_dump())
    )
    await process_webhook(payload)
    return {"status": "ok"}

更彻底的解决方案是直接换成异步SDK(比如aioboto3),让整个调用链都是异步的。改完之后,吞吐量从42 RPS提升到了210 RPS ------5倍提升

这个案例的价值在于,它既说明了FastAPI的能力上限很高,也诚实地暴露了异步编程的学习门槛。FastAPI很快,但它不会自动帮你解决阻塞问题。 如果你不理解"什么代码会阻塞事件循环",写出来的异步服务可能比同步还慢。这不是框架的问题,是使用方式的问题。

其他采用案例

  • Sophos(网络安全公司)的SecOps AI团队:用FastAPI构建AI驱动的安全分析服务,异步编排多个威胁检测模型。
  • Boosted.ai:金融AI平台,用FastAPI支撑每天50,000+个AI投资研究工作流。
  • Netflix内部工具:大量内部API服务用FastAPI构建,尤其是需要对接ML模型的场景。

横向对比:到底该选谁?

我不想写"FastAPI吊打一切"的文章。那是不负责任的营销文。我们诚实一点,把三个框架摆在台面上比一比:

维度 FastAPI Flask Django
底层协议 ASGI(原生异步) WSGI(同步) WSGI/ASGI(3.1+支持异步)
请求校验 Pydantic自动校验 需手动/扩展 DRF Serializer
API文档 原生Swagger/ReDoc 需flask-restx/APIFairy 需DRF + drf-yasg
内置ORM 无(自由选择) Django ORM(成熟)
后台Admin 无(需sqladmin等) Django Admin(业界标杆)
异步能力 原生async/await 2.0+支持但仍WSGI 3.1+异步视图/4.1+异步ORM
典型JSON QPS ~100K+(纯序列化) ~13K ~8.5K
适合场景 API、AI服务、微服务 快速原型、小工具 CMS、电商、后台管理系统
学习曲线 中等(需懂异步思维) 较高(框架概念多)
生态规模 快速增长中 庞大 最成熟

选型建议(直接给结论)

  • 选FastAPI:如果你做的是API服务、AI/LLM应用、微服务、需要高并发I/O,或者前后端分离架构。这是它的甜蜜点。
  • 选Django:如果你要做内容管理系统、电商后台、需要内置Admin和权限系统的企业级应用,或者团队需要一套强约定来保证项目一致性。Django的"全栈电池"在这些场景里仍然无可替代。
  • 选Flask:如果你在做一个非常小的工具、内部脚本服务、快速原型验证,或者你需要最大程度的架构自由度。Flask的极简哲学依然有其价值。

一句话总结:FastAPI不是要取代Django和Flask,它是为"API-first + 异步I/O + 类型安全"这个交集里的场景提供了最优解。 恰好AI浪潮让这个交集变得越来越大。


边界:FastAPI不是银弹

最后,聊几个FastAPI不擅长、或者说你需要认真对待的事情。说清楚这些,才是对读者负责。

第一,异步编程的思维转变有真实成本。

习惯了同步编程的开发者,转到async/await模式需要理解事件循环、协程、Future、信号量这些概念。"不要阻塞事件循环"是一句简单的话,但要在几百个接口里都做到,需要训练和代码review。团队里如果有人在async函数里写了一句requests.get(),整个服务的性能就可能雪崩。

第二,FastAPI是"API-first"框架,不是"全栈"框架。

它不自带ORM(你可以选SQLAlchemy、Tortoise ORM、Prisma)、不自带Admin(可以用sqladmin)、不自带认证系统(要自己用OAuth2/JWT实现)、不自带模板引擎。这些都有成熟的第三方库,但你需要自己组装。对于喜欢"开箱即用"的团队,这会有初期选型成本。

第三,CPU密集型任务不要指望异步。

异步只能在I/O等待时帮你提高并发。如果你要做大量数学计算、图像处理、视频编码这类CPU密集任务,async不会让它变快。这种场景应该把任务丢给Celery/Arq任务队列,用多进程worker处理。

第四,多worker部署下全局变量不可靠。

uvicorn --workers 4启动时是4个独立进程,内存不共享。如果你在内存里存了一个全局字典当缓存,不同worker之间看不到彼此的数据。需要缓存请用Redis。

第五,异步生态虽然成熟,但仍有缺口。

并不是所有Python库都有异步版本。最常用的数据库(SQLAlchemy 2.0 async、asyncpg、redis-py async)、HTTP客户端(httpx)都有了,但某些小众服务的SDK可能只有同步版,需要用run_in_threadpool包装。


结语:框架的崛起,是因为它回答了时代的问题

我们回头看技术演进的历史:

  • Django在2005年崛起,因为它回答了"如何快速开发数据库驱动的新闻网站"这个问题。
  • Flask在2010年崛起,因为它回答了"如何给开发者最大自由度"这个问题。
  • FastAPI在2018年诞生、在2023-2025年爆发,因为它回答了三个 时代级问题:
    1. I/O密集型服务越来越多,同步模型撑不住了------它有ASGI和原生异步
    2. 前后端分离+团队协作规模越来越大,类型安全和文档同步是刚需------它有Pydantic和自动OpenAPI
    3. AI应用爆发,流式响应、多模型编排成为标准模式------它从底层就为此设计

FastAPI的作者Sebastián Ramírez在2018年创建这个项目时只有23岁。他当时的目标很朴素:"为什么Python的Web框架不能像Go一样快,同时保留Python的开发体验?"

他可能没想到,7年后他的框架会因为一场AI革命,成为整个Python生态增长最快的Web框架。

但这正是技术选择的有趣之处:最好的框架不一定是功能最多的,而是最恰好匹配当下需求的那个。 当你的应用需要同时调用embedding模型、向量数据库、LLM,并且要以流式方式把结果实时推给用户时------你会发现,FastAPI写出来的代码,就是这个场景下最自然、最高效的表达方式。

这不是营销。这是架构选型。


参考资料 & 延伸阅读


如果这篇文章对你有帮助,欢迎转发给正在做技术选型的同事。你在生产环境用过FastAPI吗?踩过什么有意思的坑?欢迎在评论区聊聊。

欢迎在公众号「鬼影的随笔日记」与我交流,那里有更多分享内容~

相关推荐
Java_AI工程师1 小时前
90%的人写Function Calling,只写了"把参数传给工具执行"这一步,剩下的参数校验、错误重试、超时控制、结果格式化,全是空白。
java·人工智能·程序员
Hilaku2 小时前
为什么死磕 1px 的团队,用户体验反而更差?
前端·javascript·程序员
codigger2 小时前
我用 AI 做完整项目后,总结出一套把需求钉死的工作流
ai·程序员·编程·ai编程·#人工智能
SimonKing3 小时前
SpringBoot 集成 SSE 实现服务端推送或可代替Websocket
java·后端·程序员
挖掘狂人3 小时前
用 AI 写代码别只甩一句指令,这套工作流救过我的项目
程序员·ai编程·codebuddy
Sam_Deep_Thinking3 小时前
关于java final关键字的可见性
java·后端·面试·程序员
狂师3 小时前
cloudflared,不需要服务器和公网 IP 的免费内网穿透,一条命令上手
服务器·程序员·开源
demo007x4 小时前
我用 AI 做了一个跨平台的PopClip:拾趣Magpie
程序员·github·ai编程
zzzzzz3105 小时前
每日一条技术记录:把碎片学习变成可复盘的知识卡片
程序员·markdown·沸点