应分类判断错误:*net.dnserror.isnotfound为永久失败不重试,istemporary、context.deadlineexceeded及特定syscall错误可重试;需用自定义resolver+dial模拟故障,并通过日志和tcpdump验证重试逻辑生效。

怎么判断 DNS 解析失败是否该重试
不是所有 net.LookupIP 或 net.Resolver.LookupHost 返回的错误都适合重试。直接用 errors.Is(err, &net.DNSError{}) 会漏掉 context.DeadlineExceeded 和底层 net.OpError;而把所有错误都重试,又可能把 No such host 这类永久性错误也卷进去。
-
err.(*net.DNSError).IsNotFound == true→ 永久失败,不重试 -
err.(*net.DNSError).IsTemporary == true→ 可重试(如 UDP 超时、server unreachable) -
errors.Is(err, context.DeadlineExceeded)→ 单次查询超时,可重试 -
errors.Is(err, syscall.ECONNREFUSED) || errors.Is(err, syscall.ENETUNREACH)→ 网络层临时异常,可重试 -
strings.Contains(err.Error(), "timeout")→ 需结合上下文判断,UDP 查询超时通常可重试,但 TCP 握手超时可能反映服务不可达
怎么构造一个可控的测试环境来触发 DNS 失败
本地开发时无法稳定复现“DNS 服务器无响应”或“UDP 包丢弃”,得主动模拟。别改 /etc/resolv.conf 或杀 systemd-resolved —— 容器和 CI 里行为不一致,且难控制粒度。
- 用自定义
net.Resolver+Dial函数,对特定域名(如fail.test)硬编码返回syscall.ENETUNREACH - 启动一个本地 UDP 服务监听
:53,但只对部分请求响应,其余直接丢包(可用golang.org/x/net/ipv4实现) - 在测试中用
net.ListenUDP("udp", &net.UDPAddr{Port: 53})占住端口,让真实 DNS 查询 fallback 到 TCP 后仍失败 - 最轻量:在
Dial回调里对addr == "8.8.8.8:53"的请求随机 sleep >2s 并返回context.DeadlineExceeded,模拟高延迟抖动
怎么验证重试逻辑真正生效而不是静默失败
常见陷阱是重试代码写了,但没打日志、没暴露中间错误、也没检查最终是否用了第 2 或第 3 次尝试的结果。尤其要注意 net.DefaultResolver 在 Alpine 容器里可能根本没走你写的 Dial 函数——它默认用 musl 的 getaddrinfo,绕过 Go 的 resolver 控制。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 在
Dial函数里加log.Printf("DNS dial to %s, attempt #%d", addr, attempt),确认调用次数和目标地址 - 用
tcpdump -i any port 53抓包,看是否真发出了多次 UDP 查询(注意 Go 默认 UDP 查询超时是 1s,两次之间至少有退避间隔) - 在重试循环里记录每次错误:
log.Printf("DNS attempt %d failed: %v", i+1, err),避免最后一次错误覆盖前面的线索 - 测试时显式传入
&net.Resolver{PreferGo: true},禁用 cgo resolver,确保逻辑走你写的路径
为什么并发查多个域名时容易漏掉重试效果
用 errgroup 并行调用 r.LookupHost 很常见,但如果你给每个子任务传的是同一个 ctx(比如带 3s 超时的),那第一次失败后整个 group 就被 cancel,后续重试根本不会执行。
- 每个域名查询必须用独立的
ctx,例如ctx, _ := context.WithTimeout(context.Background(), 2*time.Second) - 不要把重试逻辑塞进
errgroup.Go的闭包里——那样重试发生在 goroutine 内部,上层无法感知是否成功 - 更稳妥的做法:先用
errgroup并发发起 *已封装好重试逻辑* 的函数,每个函数自己管理自己的 ctx 和重试次数 - 验证点:查 5 个域名,故意让第 1 个在第 1 次就成功、第 2 个在第 2 次才成功,观察日志中是否出现“attempt #2”字样
重试不是越多越好,关键在错误分类是否准确和上下文是否穿透到位。很多线上问题不是没重试,而是重试了但用的是被 cancel 掉的 ctx,或者把 NXDOMAIN 当成临时错误反复查。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










