httpx.AsyncClient() 默认不复用连接,因其默认 limits 保守(最大连接数10、空闲连接仅保持5秒)、未启用 HTTP/2 且缺乏保活探测;需显式配置 limits、http2=True 及合理 timeout 才能启用长连接池。

为什么直接用 httpx.AsyncClient() 默认不复用连接
默认情况下,httpx.AsyncClient() 每次 request() 都可能新建 TCP 连接,尤其在高并发短请求场景下,反复三次握手 + TLS 握手会显著拖慢整体耗时。这不是 bug,而是因为默认 limits 设置保守:连接池大小为 10,空闲连接最多保持 5 秒,且未启用 HTTP/2 或连接保活探测。
怎么配置 httpx.AsyncClient 启用长连接池
核心是显式传入 limits 和 timeout,并确保服务端支持 keep-alive(绝大多数现代 Web 服务默认支持)。关键参数如下:
-
max_connections=100:控制最大并发连接数,避免“Too many open files”错误,建议按压测结果调整 -
max_keepalive_connections=20:限制空闲中可复用的连接数,防止连接池长期占着不用却无法释放 -
keepalive_expiry=60.0:空闲连接最长保留 60 秒,比默认 5 秒更适配多数后端的keepalive_timeout -
http2=True:启用 HTTP/2 可复用单个 TCP 连接承载多路请求,进一步减少握手开销(需服务端支持)
示例初始化:
import httpx
client = httpx.AsyncClient(
limits=httpx.Limits(
max_connections=100,
max_keepalive_connections=20,
keepalive_expiry=60.0,
),
http2=True,
timeout=httpx.Timeout(10.0, connect=3.0),
)
常见连接池失效场景和排查方法
即使配置了长连接池,仍可能退化为短连接,典型原因包括:
立即学习“Python免费学习笔记(深入)”;
- 服务端返回
Connection: close响应头 —— httpx 会主动关闭该连接,不再放入 keepalive 池 - 请求中手动设置了
headers={"Connection": "close"}—— 覆盖了默认 keep-alive 行为 - 异常未被正确处理(如未 await 或未调用
aclose())导致连接泄漏,最终触发max_connections限制而新建连接 - 使用了
client.get(url, follow_redirects=False)等非标准流程,某些重定向或鉴权逻辑可能绕过连接复用路径
调试时可开启日志观察连接行为:logging.getLogger("httpx").setLevel(logging.DEBUG),重点关注 reuse connection 或 new connection 日志行。
连接池生命周期管理必须手动控制
AsyncClient 不是上下文管理器的自动生命周期代理 —— 如果你在函数内创建 client、发起请求、然后函数结束,但没显式调用 await client.aclose(),连接不会被及时归还或清理,下次再建 client 就可能撞上系统文件描述符上限。
- 推荐在应用启动时创建全局 client 实例(如 FastAPI 的
lifespan),并在 shutdown 时await client.aclose() - 若必须函数内临时使用,务必用
async with httpx.AsyncClient(...) as client:,否则连接资源大概率泄漏 - 不要在每次请求都新建 client;也不要跨协程共享未加锁的 client 实例(虽 httpx 内部线程安全,但连接池状态共享无问题)
真正影响 TCP 握手耗时的,往往不是配置多复杂,而是连接有没有被真正“留着等下一次用”。空闲超时、服务端策略、异常路径遗漏,这三个点漏掉一个,池就形同虚设。


















