这类问题我又碰上了一次。FastAPI 0.115 里写了个普通 def 端点,内部调用 requests 拉模型接口,单请求 200ms,压测 100 并发时 P99 到 8 秒,CPU 利用率只有一成。
第一反应是「线程池被长请求占满后事件循环只能干等」,验证下来不成立。
原因不止一个,得分开看。同步 def 端点跑在线程池里,池大小默认 40;端点里用了阻塞的 requests 而不是异步客户端;没有设置超时,慢请求一直占着工作线程。
上来先确认端点是不是同步函数
grep -n 'def predict' main.py
然后改成 async def 并用 httpx 异步调用
python -c "import httpx;print(httpx.AsyncClient)"
调大线程池看 P99 是否变化以确认瓶颈
python -m uvicorn main:app --workers 1 --limit-concurrency 200
给下游调用加上 3 秒超时
python -c "import httpx;c=httpx.Client(timeout=3.0);print(c)"
这类改动我都写进变更记录,过两周再出问题时能对上号。
怎么算真的好了,看的是同一批现象。100 并发下 P99 降到 600ms 以内,事件循环不再被阻塞,CPU 利用率随并发上升而提高。
同步端点不是不能用,但下游一变慢就会把线程池耗尽,异步链路要一路异步到底,中间混一个阻塞调用就前功尽弃。报错文案容易误导,真正的原因往往在相邻那一层的日志里。
