Python 3.11 的 asyncio + aiohttp 是测 API 响应时间最轻量、最可控的组合,因其基于单线程 I/O 多路复用,避免了 requests 同步阻塞及线程/进程开销,配合连接池复用、DNS 缓存和精确计时,可实现高吞吐、低噪声的稳定压测。

Python 3.11 的 asyncio + aiohttp 是测 API 响应时间最轻量、最可控的组合;用 threading 或 multiprocessing 反而容易因 GIL 或进程开销引入噪声,不推荐。
为什么不用 requests 配合 ThreadPoolExecutor
虽然能跑起来,但 requests 是同步阻塞的,在高并发下线程大量卡在 DNS 解析、TCP 握手或等待响应阶段,线程数一多就内存暴涨、响应时间失真。Python 3.11 的 asyncio 调度器能真正复用单线程做 I/O 多路复用,吞吐更高、延迟更稳。
-
requests每个请求独占一个线程,100 并发 ≈ 100 线程,实际活跃连接可能不到 20 个 -
aiohttp.ClientSession默认复用连接池,100 并发可维持 100 个并发 TCP 连接(取决于connector配置) - Python 3.11 对
asyncio的事件循环做了性能优化(如更快的 task 创建和取消),实测比 3.9 快 15% 左右
如何用 aiohttp + asyncio.gather 批量测响应时间
核心是把每个请求包装成返回 (url, status, elapsed_ms) 的协程,再用 gather 并发触发——注意别直接 await 单个请求,那等于串行。
import asyncio
import aiohttp
import time
<p>async def fetch_with_timer(session, url):
start = time.perf_counter()
try:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=5)) as resp:
await resp.read() # 确保 body 完全接收
elapsed = (time.perf_counter() - start) <em> 1000
return url, resp.status, round(elapsed, 2)
except Exception as e:
elapsed = (time.perf_counter() - start) </em> 1000
return url, f"ERROR: {type(e).<strong>name</strong>}", round(elapsed, 2)</p><p>async def main():
urls = ["<a href="https://www.php.cn/link/5f69e19efaba426d62faeab93c308f5c">https://www.php.cn/link/5f69e19efaba426d62faeab93c308f5c</a>"] * 20 # 模拟 20 次请求
connector = aiohttp.TCPConnector(limit=50, limit_per_host=20) # 控制连接数
timeout = aiohttp.ClientTimeout(total=10)</p><pre class="brush:php;toolbar:false;">async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
results = await asyncio.gather(
*[fetch_with_timer(session, u) for u in urls],
return_exceptions=True
)
for url, status, elapsed in results:
print(f"{url} → {status} in {elapsed}ms")asyncio.run(main())
-
limit和limit_per_host必须显式设,否则默认只允许 100 个总连接、每 host 最多 100 个,压测时可能被服务端限流或触发重试 -
await resp.read()不能省:否则elapsed只计到 header 收完,不包含 body 传输时间 -
return_exceptions=True是关键,避免一个请求失败导致整个gather中断
怎样识别并排除 DNS 和 TLS 握手干扰
真实响应时间 ≠ 网络层耗时。如果你只关心后端处理速度,得把 DNS 查询、TCP 建连、TLS 握手这些“固定开销”剥离开——尤其当目标域名没预热、或证书链长时,它们可能占到总耗时 60% 以上。
- 用
aiohttp.TCPConnector(resolve_cache=True)开启 DNS 缓存,避免重复查 - 对同一 host 多次请求时,
limit_per_host要够大,确保连接复用(HTTP/1.1 keep-alive 或 HTTP/2 stream 复用) - 如果要测纯业务逻辑,先用
curl -w "@format.txt" https://api.example.com/health粗略看各阶段耗时,确认time_connect和time_appconnect是否稳定 - Python 3.11 不支持在
aiohttp中直接获取各阶段毫秒级细分时间,如需精确拆解,得换hypercorn+ 自定义 client 或改用 Rust 写的reqwest+ Python 绑定
真正难的不是并发发请求,而是让每次测量都落在同一个网络路径、同一个连接状态、同一个 TLS 会话上——稍不注意,你看到的“P95 响应时间”其实是 DNS 波动+证书刷新+服务端 GC 的混合噪音。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











