aiohttp默认使用同步socket.getaddrinfo导致事件循环阻塞,必须用aiodns.dnsresolver替换并显式设use_dns_cache=true,否则高并发下dns查询会卡死整个异步流程。

直接换掉 aiohttp 默认的同步 DNS 解析器,用 aiodns.DNSResolver 替代,并显式开启 DNS 缓存 —— 否则高并发下大量 getaddrinfo 调用会卡死事件循环,延迟波动剧烈,甚至触发假超时。
为什么 aiohttp 默认 DNS 解析会拖慢异步请求
aiohttp 底层调用的是 Python 标准库的 socket.getaddrinfo,这是个同步阻塞函数。哪怕你发 100 个 session.get(),只要其中一次 DNS 查询卡住(比如本地 DNS 响应慢、丢包、返回 NXDOMAIN),整个 asyncio 事件循环就停在那儿不动,所有后续协程都得等。
典型现象包括:
-
TimeoutError频繁抛出,但用curl -v或浏览器直连目标域名却秒开 -
strace -e trace=getaddrinfo,connect显示大量时间耗在getaddrinfo系统调用上 - 同一
ClientSession并发请求不同域名时延迟方差极大,而只请求单个域名时很稳定
如何正确配置 aiodns + aiohttp
关键不是“手动查完 IP 再拼 URL”,而是让 aiohttp 在建连阶段自动走异步 DNS 流程。这需要替换 TCP 连接器的 resolver 实现:
- 必须传入
aiodns.DNSResolver()实例给aiohttp.TCPConnector(resolver=...),只装aiodns包不配置无效 - 务必显式设置
use_dns_cache=True——aiohttp默认开启缓存,但aiodns.DNSResolver不继承该行为,漏设会导致每次请求都重新查 DNS - 如需指定 DNS 服务器(比如绕过污染或加速),初始化时加
nameservers=['8.8.8.8', '223.5.5.5'] - Windows 用户注意:需安装 Visual C++ Build Tools 或使用预编译 wheel,否则
pip install aiodns可能编译失败
示例代码片段:
import aiohttp
import aiodns
resolver = aiodns.DNSResolver(nameservers=['8.8.8.8'])
connector = aiohttp.TCPConnector(resolver=resolver, use_dns_cache=True)
async with aiohttp.ClientSession(connector=connector) as session:
async with session.get('https://example.com') as resp:
ttl_dns_cache 和 limit_per_host 怎么配才不被 DNS 拖垮
DNS 不只是解析慢,更是连接池的隐形瓶颈。如果 limit_per_host=0(默认),所有请求都挤向同一域名,DNS 查询队列会堆积;而 limit 控的是总连接数,不控单域名并发量。结果就是:你设了 limit=100,但 80 个都在查 example.com 的 IP,其余域名全在等。
- 务必设置
limit_per_host,比如limit_per_host=10,防止单域名 DNS 请求雪崩 -
limit应 ≥limit_per_host × 域名数量,但别盲目拉高;实测超过 50 后吞吐常下降 - 若目标网站只有一两个域名,
limit_per_host比limit更关键
Windows 上 aiodns 初始化失败怎么办
Windows 下 aiodns 依赖 pycares,不装会直接报 ImportError: No module named '_cares' 或初始化失败。
- 必须执行
pip install pycares,再装aiodns - 若仍失败,优先用预编译 wheel:
pip install --only-binary=all aiodns - 避免用
conda安装aiodns,它自带的构建链经常和 Windows SDK 版本不兼容
真正容易被忽略的是:即使你用了 AsyncResolver,如果没设 use_dns_cache=True,它仍然每轮都发新查询 —— 这不是异步不异步的问题,是根本没启用缓存开关。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











