0 Comments

FastAPI 高性能 Web 框架实战:从请求到数据库一条龙

很多朋友一提到 Python 做 Web 开发,脑子里冒出来的还是 Flask 和 Django。这俩确实经典,但如果你要写接口、做前后端分离,FastAPI 这几年已经成了新的默认选项。我最近把手头一个内部服务的接口层从 Flask 迁到了 FastAPI,踩了一些坑,也尝到了甜头。这篇把核心的东西理一理。

为什么是 FastAPI

先说几个实打实的好处,不带水分:

  • 快。 底层是 Starlette + Pydantic,异步支持是一等公民,很多场景吞吐量能甩开同步框架一个量级。
  • 类型即校验。 用 Python 类型注解声明请求和响应,Pydantic 自动帮你校验、序列化,顺便生成 OpenAPI 文档。
  • 文档白送。 写完直接去 /docs 就能看到可交互的 Swagger 界面,接前端的人自己就能对着文档调。

缺点也有:生态不像 Django 那么全家桶,很多东西(ORM 迁移、后台管理)得自己拼。不过对纯 API 服务来说,这反而清爽。

起步:一个最小的应用

先装包:

pip install fastapi uvicorn

写个 main.py

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def read_root():
    return {"hello": "world"}


@app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None):
    return {"item_id": item_id, "q": q}

跑起来:

uvicorn main:app --reload

打开 http://127.0.0.1:8000/docs,你会看到自动生成的接口文档,参数、类型一目了然。注意上面那个 item_id: int——如果你访问 /items/abc,FastAPI 会直接返回 422 校验错误,而不是像 Flask 那样你得自己判断造轮子。

请求体与数据校验

这是 FastAPI 最舒服的地方。用 Pydantic 模型声明请求体,校验逻辑全自动:

from pydantic import BaseModel, Field


class Item(BaseModel):
    name: str
    price: float = Field(gt=0, description="价格必须大于 0")
    tags: list[str] = []


@app.post("/items")
def create_item(item: Item):
    return {"item_name": item.name, "price": item.price}

如果前端传了 price: -3,直接 422,错误信息还告诉你哪个字段出问题。以前写 Flask 那种手工 request.get_json() 再加一堆 if 判断的日子,真的回不去了。

异步数据库访问

FastAPI 的价值要配合异步数据库驱动才真正体现。以 SQLAlchemy 2.0 的 async 模式 + asyncpg 为例:

from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker

DATABASE_URL = "postgresql+asyncpg://user:pass@localhost/db"

engine = create_async_engine(DATABASE_URL, echo=True)
AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)


async def get_db():
    async with AsyncSessionLocal() as session:
        yield session

然后在路由里注入:

from fastapi import Depends
from sqlalchemy import text


@app.get("/users")
async def list_users(db: AsyncSession = Depends(get_db)):
    result = await db.execute(text("SELECT id, name FROM users"))
    return [dict(row) for row in result.mappings()]

Depends 是 FastAPI 的依赖注入,get_dbyield 出去的东西在请求结束后会自动清理。这个模式写熟了,代码会非常干净。

踩过的几个坑

坑一:同步代码会拖垮异步路由。 如果你在 async def 路由里调了一个同步的阻塞函数(比如 time.sleep 或者同步的 requests 请求),整个事件循环都会被卡住。这种情况要么用 def(不用 async 声明)让 FastAPI 丢到线程池,要么用 await asyncio.to_thread(...)

坑二:Pydantic v2 的迁移。 FastAPI 现在默认用 Pydantic v2,老项目里的 Config 类写法要改成 model_configorm_mode 也变成了 from_attributes=True。网上很多旧教程还是 v1 语法,照着抄会报错。

坑三:Depends 的作用域。 依赖默认是请求级缓存的,同一个请求里多次 Depends(get_db) 会复用同一个连接。想要全局单例得自己用 lru_cache 包一层,别想当然。

部署建议

生产环境别再用 --reload 了。一般用多 worker:

uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4

更进阶的是用 Gunicorn 管 uvicorn worker,再前面套一层 Nginx 做反代和 TLS。FastAPI 本身不自带静态文件托管,遇到静态资源要么用 StaticFiles 挂载,要么干脆交给 Nginx。

小结

FastAPI 给我的感觉是:类型注解写起来确实比无类型的 Flask 多花几秒钟,但这几秒钟换回来的是自动校验、自动文档,还有异步带来的实打实的性能提升。如果你下一个项目是写 API,不妨从它开始。跟 Flask 比它更现代,跟 Django 比它更轻,定位卡得刚刚好。

整篇代码都能直接跑,建议照着敲一遍再改,比光看概念有用得多。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注