FastAPI高并发场景下httpx性能劣于aiohttp的原因及代码排查请求
FastAPI同步异步路由测试:httpx复用实例性能低于aiohttp问题排查
在FastAPI框架中进行同步与异步路由测试时,发现复用同一客户端实例的httpx在高并发请求场景下性能远不如aiohttp,现针对代码问题排查如下:
原测试代码
import uvicorn from fastapi import FastAPI import requests import httpx import aiohttp app = FastAPI(description="sync and async route func test") aio_client = httpx.AsyncClient() aio_session: aiohttp.ClientSession = None req_session = requests.Session() @app.on_event("startup") async def startup_event(): global aio_session aio_session = aiohttp.ClientSession() async def async_get(url): # async with aio_session.get(url) as resp: # return resp resp = await aio_client.get(url) return resp @app.get("/ping") async def ping(): return "pong" @app.get("/async_route_func_demo") async def async_route_func(): url = "https://juejin.cn/" resp = await async_get(url) # print("resp", resp) return "async_route_func_demo" @app.get("/async_route_use_sync_io_demo") async def async_route_func(): url = "https://juejin.cn/" resp = req_session.get(url) # print("resp", resp) return "async_route_func_demo" @app.get("/sync_route_func_demo") def sync_route_func(): url = "https://juejin.cn/" resp = req_session.get(url) # print("resp", resp) return "sync_route_func_demo" def main(): uvicorn.run(app, log_level="warning") if __name__ == '__main__': main()
环境信息
- Python版本:3.10.12
- 操作系统:macOS M1 Pro
- 依赖版本:
fastapi==0.101.0 uvicorn==0.23.2 requests==2.32.0 httpx==0.27.0
wrk测试结果
高并发测试显示,使用复用httpx异步客户端实例的路由接口,QPS、延迟等核心性能指标均显著落后于使用aiohttp客户端的版本。
问题排查与修正
原代码核心问题
- httpx客户端初始化时机错误:在模块级别直接创建
httpx.AsyncClient(),未在FastAPI的异步启动上下文内完成初始化,导致客户端与事件循环绑定异常,高并发场景下易出现资源竞争或阻塞。 - 未正确销毁httpx客户端:仅在启动事件中初始化了aiohttp会话,但未在服务关闭时关闭httpx客户端,会引发连接池资源泄漏,长期运行后性能持续下降。
- 对比测试逻辑未启用:原代码中aiohttp的调用逻辑被注释,无法直接完成两者的性能对比验证。
修正后的代码
import uvicorn from fastapi import FastAPI import requests import httpx import aiohttp app = FastAPI(description="sync and async route func test") aio_client: httpx.AsyncClient = None aio_session: aiohttp.ClientSession = None req_session = requests.Session() @app.on_event("startup") async def startup_event(): global aio_session, aio_client # 在异步启动上下文内初始化客户端,确保与事件循环正确绑定 aio_session = aiohttp.ClientSession() # 对齐aiohttp默认连接池配置,缩小性能差异 aio_client = httpx.AsyncClient( limits=httpx.Limits(max_keepalive_connections=100, max_connections=200) ) @app.on_event("shutdown") async def shutdown_event(): # 关闭所有客户端,释放资源 await aio_session.close() await aio_client.aclose() req_session.close() async def async_get_httpx(url): resp = await aio_client.get(url) return resp async def async_get_aiohttp(url): async with aio_session.get(url) as resp: return resp @app.get("/ping") async def ping(): return "pong" @app.get("/async_route_httpx") async def async_route_httpx(): url = "https://juejin.cn/" await async_get_httpx(url) return "async_route_httpx" @app.get("/async_route_aiohttp") async def async_route_aiohttp(): url = "https://juejin.cn/" await async_get_aiohttp(url) return "async_route_aiohttp" @app.get("/async_route_use_sync_io") async def async_route_use_sync_io(): url = "https://juejin.cn/" resp = req_session.get(url) return "async_route_use_sync_io" @app.get("/sync_route_func") def sync_route_func(): url = "https://juejin.cn/" resp = req_session.get(url) return "sync_route_func" def main(): uvicorn.run(app, log_level="warning") if __name__ == '__main__': main()
额外说明
httpx默认连接池配置与aiohttp存在差异,初始化时显式设置limits参数对齐两者的连接池大小,可进一步缩小性能差距。
内容的提问来源于stack exchange,提问作者hui
相关产品推荐
相关产品推荐

