aiohttp 默认用同步 socket.getaddrinfo 做 DNS 解析,阻塞事件循环,高并发多域名场景下成性能瓶颈;需通过 aiodns.DNSResolver 替换 TCPConnector 的 resolver 并启用 use_dns_cache=True 才能实现异步 DNS。

为什么 aiohttp 默认 DNS 解析会拖慢并发请求
aiohttp 默认用 socket.getaddrinfo 做 DNS 查询,这是同步阻塞调用 —— 即便你在 async def 里发 100 个请求,只要某次 DNS 查询卡住(比如超时或网络抖动),整个事件循环就可能被拖慢。尤其在批量爬取不同域名时,DNS 成为明显瓶颈。
根本原因:Python 标准库的 DNS 解析不支持异步,aiohttp 没有默认集成异步 DNS 客户端。
- 现象:高并发下
aiohttp.ClientSession的get()延迟波动大,timeouts频繁触发,但实际目标服务器响应很快 - 验证方式:用
strace -e trace=connect,getaddrinfo观察系统调用,能看到大量阻塞在getaddrinfo - 兼容性注意:Windows 上
aiodns依赖pycares,需确保安装了 Visual C++ Build Tools 或使用预编译 wheel
如何用 aiodns 替换 aiohttp 的 DNS 解析器
关键不是“手动调用 aiodns”,而是让 aiohttp 在创建连接时自动走异步 DNS 流程。这需要替换其底层 Resolver 实现。
实操要点:
- 必须用
aiodns.DNSResolver实例传给aiohttp.TCPConnector的resolver参数,不能只装包不配置 -
aiodns默认使用系统 nameserver(/etc/resolv.conf或 Windows 注册表),如需指定 DNS 服务器,初始化时加nameservers=['8.8.8.8'] - 务必设置
use_dns_cache=True(aiohttp 默认开启,但 aiodns 不自动继承),否则每次请求都查 DNS,失去缓存意义 - 示例代码片段:
import aiohttp
import aiodns
resolver = aiodns.DNSResolver()
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:
...
aiodns + aiohttp 组合下的常见报错与修复
最常遇到的不是功能不工作,而是错误被静默吞掉或表现诡异。
-
DNSResolveError: Domain name not found:不是网络问题,很可能是目标域名不存在 A/AAAA 记录,或本地 DNS 服务器返回了 NXDOMAIN;可加raise_for_status=False后检查resp.status和异常类型 -
AttributeError: 'DNSResolver' object has no attribute 'resolve':旧版 aiodns(pip show aiodns,升级到>=2.0.0 - 并发数设太高(如 >500)时出现
OSError: [Errno 24] Too many open files:aiodns 内部用 socket,受系统文件描述符限制;需调大 ulimit,或改用limit_per_host控制单域名并发 - 部分域名解析成功、部分超时:检查是否混合了 IPv6 域名,
aiodns默认查 A+AAAA,若目标只支持 IPv4,可设family=socket.AF_INET强制 IPv4
性能对比和要不要上 aiodns 的判断依据
不是所有场景都值得引入 aiodns。它真正起作用的前提是:你有大量不同域名的并发请求,且这些域名未被缓存过。
- 测试方法:用相同 URL 请求 100 次 vs 100 个不同子域名(如
sub1.example.com…sub100.example.com),对比总耗时;后者差异通常达 2–5 倍 - 如果全部请求同一域名(如只爬 api.github.com),DNS 缓存已覆盖,加 aiodns 几乎无提升,反而增加依赖和初始化开销
- 替代方案:用
trust_env=True+ 系统级 DNS 缓存(如 systemd-resolved 或 dnsmasq),比 aiodns 更轻量,但可控性差 - 注意:aiodns 不支持 HTTPS DNS(DoH),如有隐私或防火墙穿透需求,需额外封装
真正容易被忽略的是 DNS 缓存生命周期 —— aiohttp 默认缓存 300 秒,但 aiodns 自身不管理 TTL,全靠你传入的 use_dns_cache 和 connector 的 ttl_dns_cache 参数协同;设错会导致缓存击穿或陈旧解析。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











