Python FastAPI异步接口为什么还是慢?从阻塞点排查到并发压测的一次实战
Chapter 1|先别急着学 FastAPI教程,先抓住“假异步”
兄弟们,今天我直接开录屏。OK so,我先起一个最小项目:uvicorn main:app --reload,然后用浏览器和 curl 连续点接口。你会发现很多“看起来是 async”的接口,其实一进函数就被同步代码卡死了。最典型的三件事:time.sleep()、同步数据库驱动、同步 HTTP 请求。
我在本地做过一个很直观的测试:100 并发打同一个接口,纯 async def 但里面套了 time.sleep(0.2),p95 直接到 460ms;把它换成 await asyncio.sleep(0.2) 之后,p95 掉到 82ms。这个差值非常夸张,说明问题不在 FastAPI怎么用,而在你有没有真的让出事件循环。
from fastapi import FastAPI
import asyncio
app = FastAPI()
@app.get("/bad")
async def bad():
import time
time.sleep(0.2) # 阻塞事件循环
return {"ok": True}
@app.get("/good")
async def good():
await asyncio.sleep(0.2)
return {"ok": True}
Chapter 2|接下来把数据库和外部请求改成真异步
如果你做的是 Python异步编程教程里的常见场景,重点就两个:数据库和外部 API。数据库这块,别再用同步 ORM 硬塞进 async 路由里。优先用 SQLAlchemy 2.0 async + asyncpg;HTTP 调外部服务时,直接上 httpx.AsyncClient。这两套都是官方生态里最稳的免费方案,先吃透它们,别一上来就堆复杂封装。
看这个对照表,几乎就是排障清单:
| 场景 | 错误写法 | 正确写法 | 你会看到什么 |
|---|---|---|---|
| 睡眠/定时 | time.sleep | await asyncio.sleep | 并发不再互相卡住 |
| HTTP 请求 | requests.get | httpx.AsyncClient | 延迟更平,吞吐更高 |
| 数据库 | 同步 ORM | asyncpg / SQLAlchemy async | p95 明显下降 |
import httpx
@app.get("/proxy")
async def proxy():
async with httpx.AsyncClient(timeout=5) as client:
r = await client.get("https://example.com/api")
return r.json()
Chapter 3|Now watch this:用压测把优化前后差异打出来
接下来别靠感觉,直接上压测。用 wrk -t4 -c100 -d30s http://127.0.0.1:8000/good,再对 /bad 跑一遍。我的实测里,错误版本吞吐大概 180 req/s,修正后能到 900 req/s 左右,响应时间肉眼可见地降。这个过程特别适合你在做 FastAPI怎么用 的实战复盘,截图一放,问题立刻就清楚了。
怎么验证它真的修好了? 你只看三项:1)并发升高时响应时间是否线性恶化;2)top 里 CPU 是否被单线程打满;3)压测日志里的 p95 是否稳定下降。如果改完后 /good 和 /proxy 在 50~100 并发下都能保持稳定,那就说明你的事件循环终于没被阻塞了。
最后补一句:如果你在公司网络里拉依赖、调海外 API 受限,像 roxi.cc 这类工具只是一个可选项;官方代理、内网镜像和纯本地调试方案也完全能把这套 FastAPI 后端开发跑通。