从卡顿到丝滑:FastAPI 调用外部 API 的正确姿势(httpx 实战) 不难想像问题有很大概率出在你随手 import 的那个 requests 身上。先不急着看代码一起把这件事从头捋一捋了解下背后的原理这样才能做到应对不同突发情况时心里都有个谱。 本文能帮你解决什么读完这篇你基本上就知道了 FastAPI 里到底该用什么工具去访问其他的 API 拿数据其实主要是学会怎么避开同步阻塞的大坑还有就是 httpx 这个六边形战士怎么用、怎么配。也算一些经验的总结‍我是爱折腾的一名程序媛喜欢研究全栈开发的各种实践热爱分享踩坑后的收获与思考也享受用代码写出各种实用小工具解决问题的快乐。如果你也在技术这条路上向前走关注我愿我们能彼此陪伴一起成为更好的自己 ️ 主要内容脉络 揪出让 FastAPI 变慢的真凶 三款常用 HTTP 客户端横评 httpx 安装与异步调用实战 那些一踩就炸的坑和最佳实践 总结与互动 第一部分问题与背景——别让同步调用背刺你的并发FastAPI 本身是基于 asyncio 的异步框架像闪电一样快。但如果你在 async def 的异步接口函数里直接调用 requests.get() 这种同步阻塞代码那就相当于在高速公路的快车道上停了一辆拖拉机那就只能中午出门了因为早晚会出事。requests 会把当前线程整个阻塞住等待网络 IO 返回。而事件循环里的其他任务就全被卡在后面了无法被调度。那了就没啥并发的事了一秒回到解放前。早期会使用一些小技巧“用 asyncio.to_thread 把 requests 包起来不就行了”这确实能缓解但那也只是贴个创可贴。线程切换是有开销的而且当调用量一大起来线程池本身又会成为瓶颈。结果就是治标不治本。⚔️ 第二部分工具横评——到底谁才是真命天子目前主流的在 FastAPI 里调用外部 API 的工具就这三种 requests同步老炮接口简洁生态完美。可惜它不属于异步时代。 aiohttp纯异步性能强悍。但缺点也很扎眼——原生不支持 HTTP/2API 的设计上偏底层写起来代码量有点大而且会话管理容易漏关之前写代码时就出现过因为没关 ClientSession 结果导致文件描述符泄漏…… httpx新晋卷王同时支持同步和异步API 几乎是复刻 requests上手零门槛。而且原生支持 HTTP/1.1 和 HTTP/2内置连接池和 FastAPI 简直是天作之合。说句主观的顺手的工具才是最好的httpx 让我能用写 requests 的习惯享受异步的红利还要啥自行车你可能又会说requests 早就被丢一边去了还管它什么习惯但它在咱们用 Python 写爬虫时还是有大用的异步是好但不能解决所有问题️ 第三部分实战演示——装上 httpx直接开干安装上面还是老样子包管理一步搞定$ pip install httpx但如果你需要 HTTP/2 支持就把 httpx 换成 httpx[http2] 特定包就行。接下来重点来了怎么在 FastAPI 里写一个漂亮的异步调用呢看代码from fastapi import FastAPI, Depends, Requestfrom contextlib import asynccontextmanagerimport httpx生命周期里全局定义一个异步客户端实例用完记得关asynccontextmanagerasync def httpx_lifespan(app: FastAPI):try: app.state.client httpx.AsyncClient( base_urlhttps://api.example.com, timeout10.0) logger.info(f✅ Httpx客户端初始化完成!) except Exception as e: logger.error(f❌ Httpx客户端初始化失败: {e}) raise yield if hasattr(app.state, client): await app.state.client.aclose() logger.info(f✅ Httpx客户端已关闭)app FastAPI(lifespanhttpx_lifespan)核心通过依赖函数获取客户端async def get_http_client(request: Request) - httpx.AsyncClient:return request.app.state.clientapp.get(“/user/{user_id}”)async def get_user(user_id: int, client: httpx.AsyncClient Depends(get_http_client)):resp await client.get(f/users/{user_id}) resp.raise_for_status() # 非200直接抛异常好习惯 return resp.json()看出来没跟 requests 像得亲兄弟似的只不过多了个 await。利用 FastAPI 的 lifespan 生命周期管理全局复用连接池性能上也可以直接拉满。这份代码你简单修改下里面的base_url基础链接就可以用在你的项目上啦 第四部分注意事项与进阶思考——那些我踩过的坑代码跑起来容易但怎么让它跑得稳才是真本事。下面这些点都是血的教训呀最好了解下心里有个数。 一定、一定、一定要设超时默认情况下httpx 的超时是 5 秒但很多服务调用你等不了那么久。更可怕的是如果你忘了设早期版本甚至默认无限等待你在那刷半天数据就是出不来咋回事都不知道。所以在建客户端时最好把 timeout10.0 写得明明白白这是保命符呀。 连接池耗尽比你想的常见你可能会问“我都异步了怎么偶尔还卡” 99% 是连接池满了。httpx 默认连接池上限 100。如果下游接口响应慢100 个连接全占着新请求就只能干等。最好的处理方式是根据你的并发量适当调大一下 limits 参数limits httpx.Limits(max_connections200, max_keepalive_connections50)client httpx.AsyncClient(limitslimits) 别在响应里找不着北正常我们获取返回数据都是直接使用resp.json() 但它是解析 JSON 的如果下游返回 204 No Content或者返回了个空 HTML那这行就会炸掉。最好的处理方式是先判断 resp.status_code或者用 resp.text 手动处理稳一手。如果你就就是想要 json 最好也是加个 try/catch能用json就json不行咱就text 状态码的细节resp.raise_for_status() 能在 4xx/5xx 时立刻抛异常避免后续代码空转。这个习惯建议刻在肌肉记忆里。它能帮你解决很多莫名其妙的问题 再说个容易翻车的点不建议在每个接口里临时 async with httpx.AsyncClient() as client: 去创实例。每次都建连接、拆连接TCP 握手开销巨大那你的微服务可能秒变“假异步”了虽然实际测试时它内部维护了连接池开销还好但效果终究差点意思。最好采用全局实例 事件管理这个才是正道。 做个总结说到底FastAPI 里的网络调用核心就一句话用异步客户端全局管理设好超时看好连接池。httpx 目前是我项目里的首选不过技术没有银弹如果你是追求极致吞吐量的场景花点心思调教 aiohttp 也未尝不可因为这个老将在能打方面还是胜过新兵的。但大多数时候httpx 的简洁与强大已经完全足够的了。这篇分享都是我在项目里一个Bug一个Bug改出来的经验总结希望能帮你在写这类需求时少走点弯路。