前段时间借助FastAPI 搭建了一个本地词典API ,用来翻译专业词汇和单词详细释意,但词典未收录的单词就没招了,于是又借助LibreTranslate 实现了离线翻译,这下内容是都能出结果了,但两套API不在一个接口里,把它集成到Pot 和Koodo Reader等使用时,还是不方便。
今天就借助httpx 把他们集成在一起,让后台自动判断,本地词典找不到,那就从argos里拿,而不是我在外面一个搜索不到再开别一个窗口搜索。
📝 文章摘要
本文先简单说明下 requests、aiohttp 与 httpx 的优劣以及选择建议,然后使用 httpx 从安装到能写出异步客户端,同时分享下连接池耗尽、超时未设等实际开发中可能出现的踩坑经验。花点时间认真读完,这样以后你再写微服务调用时就会又快又稳,也不用再背锅。
🤔 你写的接口,到底慢在哪?
你可能也遇到过这种诡异场景:
本地跑得飞快的 FastAPI 服务,一上测试环境就卡得跟啥似的,CPU 还没跑满,请求就已经像是假期景点在排队等着上厕所一样。
结果你打开日志一看,好家伙,时间全耗在了调用那些下游 API 上。
不难想像,问题有很大概率出在你随手 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/2,API 的设计上偏底层,写起来代码量有点大,而且会话管理容易漏关
之前写代码时就出现过因为没关 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, Request
from contextlib import asynccontextmanager
import httpx
生命周期里全局定义一个异步客户端实例,用完记得关
@asynccontextmanager
async def httpx_lifespan(app: FastAPI):
try:
app.state.client = httpx.AsyncClient(
base_url="https://api.example.com", timeout=10.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(lifespan=httpx_lifespan)
核心:通过依赖函数获取客户端
async def get_http_client(request: Request) -> httpx.AsyncClient:
return request.app.state.client
@app.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 秒,但很多服务调用你等不了那么久。
更可怕的是,如果你忘了设,早期版本甚至默认无限等待,你在那刷半天,数据就是出不来,咋回事都不知道。
所以,在建客户端时,最好把 timeout=10.0 写得明明白白,这是保命符呀。
🔹 连接池耗尽比你想的常见
你可能会问:"我都异步了,怎么偶尔还卡?" 99% 是连接池满了。
httpx 默认连接池上限 100。如果下游接口响应慢,100 个连接全占着,新请求就只能干等。
最好的处理方式是,根据你的并发量,适当调大一下 limits 参数:
limits = httpx.Limits(max_connections=200, max_keepalive_connections=50)
client = httpx.AsyncClient(limits=limits)
🔹 别在响应里找不着北
正常我们获取返回数据,都是直接使用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改出来的经验总结,希望能帮你在写这类需求时少走点弯路。
如果你觉得对你有那么一...点点收获 ,那顺手点击收藏加个关注 ,以防下次用的时候划拉半天找不到在哪看过,也让我看到我的辛苦总结还是有用的,那我将甚感欣慰!
你还有什么踩坑经历或者不同意见,也欢迎评论区里聊聊,咱们一起学习,一起进步、成长!🎯