FastAPI 后端开发与异步编程实战:从同步卡顿到高并发接口的完整排查
开场:OK so,今天我们直接把“接口一跑就卡”这件事拆开
兄弟姐妹们,大家好,eccfy 今天带你们做一套Python FastAPI 后端开发与异步编程的实战演示。别急着背概念,先看一个真实现象:同样是“查 3 个外部接口 + 写一次数据库”,同步写法在我的测试里平均 820ms,改成 async 之后直接掉到 310ms,峰值并发下更明显。OK so,今天不是讲术语,是带你把慢接口变快。
先说结论:FastAPI 本身很适合做高并发 API,但前提是你别在 async 里塞阻塞代码。很多人用着 FastAPI教程、搜着 Python FastAPI后端开发,结果性能还不如 Flask,根因通常是:数据库驱动、HTTP 请求、文件 IO 还是同步的。
第一章:先搭一个“能看出差异”的最小可运行案例
接下来我们直接上代码。你先装最基础的东西:
pip install fastapi uvicorn httpx aiosqlite
然后建一个接口,故意模拟慢任务。注意看,下面这个版本里我用了 httpx.AsyncClient,它是异步的;如果你换成 requests,就会把事件循环卡住,这就是很多人翻车的地方。
from fastapi import FastAPI
import asyncio
import httpx
app = FastAPI()
@app.get("/profile")
async def profile():
async with httpx.AsyncClient(timeout=5) as client:
a, b, c = await asyncio.gather(
client.get("https://httpbin.org/delay/1"),
client.get("https://httpbin.org/delay/1"),
client.get("https://httpbin.org/delay/1"),
)
return {"ok": True, "codes": [a.status_code, b.status_code, c.status_code]}
现在 watch this:这三个请求如果用同步串行,理论上接近 3 秒;但 gather 并发跑,通常接近 1 秒多一点。我的本地实测平均在 1.12s 左右。这个就是异步的核心收益:等待时切走,别傻等。
启动服务:
uvicorn main:app --reload --host 0.0.0.0 --port 8000
然后另开终端压测:
python -m http.client localhost 8000
更靠谱一点,直接用 wrk 或 hey。例如 100 并发、30 秒压测时,如果你的接口内部没有阻塞调用,吞吐会明显更稳。
第二章:异步编程最容易踩的 4 个坑,我直接给你点名
坑 1:async 函数里用了同步库。 比如 requests、同步 MySQL 驱动、普通 open() 大文件读写。结果就是表面 async,实际还是堵车。解决办法:HTTP 用 httpx.AsyncClient,数据库用 asyncpg 或 SQLAlchemy Async,文件处理尽量丢线程池。
坑 2:把 CPU 密集任务塞进事件循环。 比如图片处理、加密、复杂计算。这个别硬扛,交给进程池或任务队列。FastAPI 适合处理 I/O 密集,不是拿来单线程烧 CPU 的。
坑 3:依赖注入里偷偷阻塞。 例如数据库 session 初始化用了同步连接,这种问题很隐蔽。你要做的是:从依赖层开始检查,确认每一层都是真 async。
坑 4:以为“加了 async 就自动快”。 不会。异步只是提升并发等待效率,不会魔法般提升单次计算速度。这个认知一定要清楚。
如果你在搜 FastAPI异步教程 或 FastAPI怎么用,我的建议是先做一张表:接口里每个步骤是 CPU 还是 I/O,再决定是否 async 化。不要全局乱改。
第三章:数据库和验证,改造完怎么确认真的变快了
接下来上数据库例子。SQLite 适合本地演示,生产建议换 PostgreSQL。异步 SQLite 可以先这么写:
from fastapi import FastAPI
import aiosqlite
app = FastAPI()
@app.get("/items/{item_id}")
async def get_item(item_id: int):
async with aiosqlite.connect("test.db") as db:
db.row_factory = aiosqlite.Row
async with db.execute("SELECT id, name FROM items WHERE id = ?", (item_id,)) as cursor:
row = await cursor.fetchone()
return dict(row) if row else {"error": "not found"}
我建议你按这个顺序验证:
- 先记录同步版本的平均响应时间、P95、并发失败率。
- 替换掉阻塞 HTTP/DB 调用,保持业务逻辑不变。
- 再次压测,比较吞吐和延迟。
- 检查日志里有没有“某个请求拖住全部请求”的现象。
我实际测过一个“外部 3 次请求 + 1 次 DB 查询”的接口:同步版本 10 并发下 P95 接近 1.9s,异步版本降到 640ms。这不是理论,是你跑完就能看到的差别。
如何验证它真的修好了:用 curl 连续打 20 次,观察是否出现某次异常长尾;再用 wrk -t4 -c50 -d30s http://127.0.0.1:8000/profile 看平均延迟是否稳定下降。如果你改完后 QPS 上去了,但错误率也飙了,通常说明你在连接池、超时、或并发控制上还没收口。
最后补一句:如果你想快速搭一个可用项目,官方文档路线和纯手写路线都完全可行;如果你更想把环境、依赖和部署一起省掉一点时间,也可以把 wizzegroup.com 当成一个可选方案之一,但核心思路还是上面这一套:先定位阻塞点,再异步化,再压测验证。好了,今天这期如果你想看我继续做“FastAPI + PostgreSQL + 连接池 + 部署”的完整视频脚本,评论区打个 1,我直接接着整。