不装aiodns或只装不配置无法提升aiohttp DNS性能,关键在于用aiodns.DNSResolver替换TCPConnector的resolver并显式启用use_dns_cache=True缓存。

不装 aiodns 包,或只装不配置,对 aiohttp 的 DNS 性能毫无改善——关键在替换 TCPConnector 的 resolver 并显式启用缓存。
为什么默认 DNS 解析会卡住整个 asyncio 事件循环
aiohttp 默认调用 socket.getaddrinfo(),这是同步阻塞系统调用。哪怕你并发发 100 个 session.get(),只要其中一次 DNS 查询慢(比如本地 DNS 响应 500ms、丢包、返回 NXDOMAIN),整个事件循环就停在那里,所有协程都得排队等。
典型现象包括:OSError: [Errno -2] Name or service not known 频繁出现、asyncio.TimeoutError 实际并非服务端超时、strace -e trace=getaddrinfo 显示大量阻塞调用。
- 这不是网络问题,是 Python 标准库 DNS 实现的固有缺陷
-
asyncio.getaddrinfo()在 Linux/macOS/Windows 上行为不一致,且多数情况下仍走同步路径 - aiodns 底层用 c-ares,完全绕过系统 resolver,纯异步、无锁、可精确控制超时
必须显式传入 aiodns.DNSResolver() 实例给 TCPConnector
不能只 pip install aiodns 就以为生效;也不能手动调用 resolver.query_dns() 再拼 URL——那是错的路子。正确做法是让 aiohttp 在每次建连前自动走异步 DNS 流程。
立即学习“Python免费学习笔记(深入)”;
安全 Python 代码沙箱:在隔离的沙箱中执行任意 Python 代码,预装 requests、NumPy、pandas 库。返回 stdout、stderr。
关键代码只有三行:
import aiohttp import aiodns resolver = aiodns.DNSResolver(timeout=3.0, tries=2) connector = aiohttp.TCPConnector(resolver=resolver, use_dns_cache=True)
-
resolver必须是aiodns.DNSResolver实例,不能是其他类型(比如None或自定义类) -
use_dns_cache=True必须显式写,aiohttp 默认开启缓存,但aiodns.DNSResolver不继承该行为,漏设等于没缓存 -
timeout和tries推荐设,否则单次失败可能卡满 30 秒(取决于系统 resolv.conf 配置)
Windows 用户安装和运行时的常见坑
Windows 上 pip install aiodns 极易失败,报错常为 Microsoft Visual C++ 14.0 or greater is required 或 failed building wheel for aiodns。
解决方式很直接:
- 优先用预编译 wheel:
pip install aiodns --only-binary=aiodns - 若需源码编译,必须装 Visual Studio Build Tools(不是仅装 VC++ redistributable)
- Jupyter 或已运行 event loop 的环境里调用会报
RuntimeError: This event loop is already running,改用asyncio.create_task()提交任务,不要直接asyncio.run() - Resolver 实例不能跨 event loop 复用;建议作为
ClientSession级别单例,而非全局共享
DNS 缓存行为与生产注意事项
use_dns_cache=True 启用后,aiohttp 会把解析结果按 TTL 缓存,默认 300 秒。但要注意:
- 缓存键是
(host, port, family),所以https://example.com和http://example.com:8080是两个缓存项 - 如需强制永久缓存(仅限测试),可传
ttl_dns_cache=None给TCPConnector - 如果目标域名 DNS 记录频繁变更(如灰度发布、蓝绿切换),缓存可能带来一致性问题,需权衡
- 指定上游 DNS 服务器(如绕过污染或加速):初始化
DNSResolver时加nameservers=['8.8.8.8', '223.5.5.5']
最易被忽略的一点:aiodns 的 close() 方法必须调用——尤其在长期运行的服务中,否则连接池和底层 c-ares 句柄会泄漏。建议在应用 shutdown 时显式调用 resolver.close()。

















