net.Resolver StrictErrors 处理部分解析结果
来源:17golang原创
时间:2026-10-10 18:37:24 457浏览 收藏
net.Resolver.StrictErrors 决定的是:Go 内置 DNS 解析器执行多个子查询时,如果其中一个发生临时错误,是否仍返回其他子查询已经得到的地址。默认值 false 偏向兼容性,例如 A 查询成功而 AAAA 查询超时,调用方仍可能拿到 IPv4 地址;设为 true 后,这类临时错误会中止整个查询,并丢弃已经收集的部分地址。
- StrictErrors 只对 Go 内置解析器生效,使用 cgo 或系统解析器时不能假设它控制结果。
- 它关注 timeout、套接字错误、SERVFAIL 等临时错误,不是“所有子查询必须都有记录”。
false适合优先保证连接可用的通用客户端,true适合不能接受单族缺失或部分发现结果的场景。- StrictErrors 不是 DNSSEC、重试开关或安全校验;启用后也必须正确处理
*net.DNSError。
背景:一次 LookupIPAddr 可能包含多个子查询
调用 LookupIPAddr 或网络类型为 ip 的 LookupIP 时,Go 内置解析器通常会查询 A 与 AAAA。配置了搜索域时,一个不带结尾点的名称还可能沿搜索列表尝试多个完整域名。对调用方而言是一次方法调用,对解析器而言却可能是多条独立请求。
独立请求意味着结果可以不对称:A 成功返回 IPv4,AAAA 返回 SERVFAIL;AAAA 成功而 A 超时;搜索列表中的一个名字发生套接字错误,另一个名字可以正常解析。StrictErrors 就是为这种“部分成功、部分临时失败”定义处理策略。

旧语义的目的:优先返回仍然可用的地址
StrictErrors 默认是 false。这不是忽略所有 DNS 错误,而是在多子查询中允许成功结果继续发挥作用。例如目标服务同时发布 A 和 AAAA,局部 DNS 设备错误处理 AAAA 查询,但 IPv4 路径完全可用;默认模式可以让客户端继续连接。
官方文档明确说明,严格模式默认不启用,是因为它可能影响与错误处理 AAAA 查询的解析器之间的兼容性。对于网页抓取器、普通 HTTP 客户端、软件更新器等“任一可达地址即可工作”的程序,默认行为往往更符合可用性目标。
需要特别区分“没有 AAAA 记录”和“AAAA 查询临时失败”。StrictErrors 控制的是后者。一个子查询正常回答无数据,不应被简单等同于 timeout 或 SERVFAIL;也不要因为启用了严格模式,就假设 A 与 AAAA 都必须返回至少一个地址。
新规则:StrictErrors=true 时临时错误中止整体查询
启用 StrictErrors 后,只要多个子查询中的任一个遇到可识别的临时错误,Go 内置解析器会让整体查询失败。当前实现还会丢弃此前收集的地址,避免网络抖动把一个双栈主机暂时表现成“只有 IPv4”或“只有 IPv6”。
| 场景 | StrictErrors=false | StrictErrors=true |
|---|---|---|
| A 成功,AAAA 超时 | 可返回 IPv4 部分结果 | 整体返回错误,不保留 IPv4 |
| A 成功,AAAA SERVFAIL | 可继续使用成功地址 | 整体返回临时错误 |
| A 无记录,AAAA 成功 | 返回 IPv6 | 通常仍返回 IPv6 |
| A 与 AAAA 都无记录 | 返回未找到错误 | 返回未找到错误 |
| 调用上下文取消 | 返回取消相关错误 | 返回取消相关错误 |
表中的“可返回”不是对所有系统解析器的承诺,它描述的是 Go 内置解析器与该字段的设计边界。临时错误是否被准确标记也依赖底层错误信息;官方文档提醒,并非所有真实的超时或临时故障都一定带有对应标志。
代码对比:只改变错误策略,不改变查询接口
下面的构造函数把 PreferGo 与 StrictErrors 放在一起。原因是 StrictErrors 针对 Go 内置解析器;若程序必须依赖这套语义,应显式优先使用内置实现,而不是让不同操作系统自行选择系统解析器。
package main
import "net"
func newResolver(strict bool) *net.Resolver {
return &net.Resolver{
// StrictErrors 只约束 Go 内置解析器,显式 PreferGo 便于跨环境保持语义。
PreferGo: true,
StrictErrors: strict,
}
}
PreferGo 表示在可用的平台上优先使用 Go 内置解析器,并不保证绕过所有操作系统限制。若自定义 Resolver.Dial 指向指定 DNS 服务,还要分别处理 UDP、TCP 回退、超时和网络策略,不能只为了 StrictErrors 随意接管。
宽松模式:通用客户端接受可用的部分地址
宽松模式的重点不是“忽略 err”,而是按返回契约同时检查地址与错误。对 LookupIPAddr 而言,成功得到地址时通常返回 nil 错误;若没有可用地址,才返回对应 DNS 错误。应用仍应为整个查询设置截止时间。
func resolveCompatible(ctx context.Context, host string) ([]net.IPAddr, error) {
resolver := newResolver(false)
// 总超时约束全部 A、AAAA 与搜索域子查询,避免解析长期占用请求预算。
lookupCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
addrs, err := resolver.LookupIPAddr(lookupCtx, host)
if err != nil {
return nil, fmt.Errorf("解析 %s 失败: %w", host, err)
}
if len(addrs) == 0 {
return nil, fmt.Errorf("解析 %s 未返回可用地址", host)
}
return addrs, nil
}
这类策略适合“有一个地址族能连接就继续”的程序。后续应把主机名交给 net.Dialer 处理多地址与双栈回退;如果先解析再只取第一个地址,仍会把 DNS 容错能力浪费掉。
严格模式:服务发现必须保持完整语义
严格模式更适合这样的需求:地址族缺失可能改变流量分配;服务发现结果要写入长生命周期缓存;运维系统需要把单族临时故障显式暴露;安全策略要求在候选集不完整时拒绝继续。它不是默认更安全,而是把“不完整”定义成不可接受。
func resolveStrict(ctx context.Context, host string) ([]net.IPAddr, error) {
resolver := newResolver(true)
lookupCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
addrs, err := resolver.LookupIPAddr(lookupCtx, host)
if err != nil {
var dnsErr *net.DNSError
if errors.As(err, &dnsErr) {
// 只记录错误类别,不把内部域名或 DNS 服务器地址无差别写入公共日志。
return nil, fmt.Errorf(
"严格解析失败 timeout=%t temporary=%t not_found=%t: %w",
dnsErr.IsTimeout,
dnsErr.IsTemporary,
dnsErr.IsNotFound,
err,
)
}
return nil, fmt.Errorf("严格解析失败: %w", err)
}
return addrs, nil
}
启用严格模式后,重试应由有预算的上层策略控制。不要看到 IsTemporary 就无限重试;应限制次数、加入抖动,并让 context 的截止时间覆盖 DNS 与后续连接。否则局部 DNS 故障会被放大成重试风暴。
兼容注意:这些情况 StrictErrors 管不到
第一,StrictErrors 不控制 cgo 或系统解析器的内部策略。若 PreferGo 没有生效,结果可能仍由 getaddrinfo 等系统接口决定。第二,它不启用 DNSSEC 验证,也不验证返回地址是否符合业务访问控制。第三,它不会增加重试次数,更不会把所有错误都改成临时错误。
第四,查询 ip4 或 ip6 时只有一个地址族子查询,StrictErrors 的“部分结果”价值会明显降低。若业务就是分别处理两个地址族,直接发起两个限族查询并各自记录结果,通常比依赖一次 ip 查询的聚合语义更清楚。
第五,搜索域会引入另一个部分结果维度。短主机名可能按 resolv.conf 的 search 列表组合多个候选名;严格模式中的临时错误可以让整个过程提前失败。服务间通信若能使用完整限定域名,通常更容易解释和观测。

如何确认当前到底用了哪个解析器
调试时可以使用 GODEBUG=netdns 查看解析器选择。数字 1 输出决策信息;go+1 在强制纯 Go 解析器的同时打印诊断。它适合短期对比,不建议长期写入生产启动参数。
# 查看当前进程选择了 Go 解析器还是系统解析器,排查结束后移除 GODEBUG=netdns=1 ./service # 临时强制 Go 内置解析器并打印决策,用于验证 StrictErrors 行为 GODEBUG=netdns=go+1 ./service
除了模式,还要记录 Go 版本、操作系统、/etc/resolv.conf 的 search/ndots/options、DNS 服务地址和查询耗时。不要只用“开关前后是否成功”作为结论,因为缓存、网络抖动和 DNS 服务器轮换都可能影响单次实验。
性能与稳定性:严格失败会改变重试压力
宽松模式可能减少整体失败,但会让流量暂时集中到一个地址族;严格模式保持结果完整性,却会把单族临时故障升级为整个请求失败。两者没有脱离业务的统一最优解。
采用前建议观测:A/AAAA 成功率、SERVFAIL 比例、解析延迟、部分结果发生率、严格失败后的重试次数、最终连接地址族和连接成功率。若启用后整体错误率明显上升,应先修复上游 DNS 或 AAAA 兼容问题,而不是无限增加重试。
采用建议
- 普通客户端、浏览器式访问、任一地址族可用即可:保留
StrictErrors=false。 - 服务发现结果会长时间缓存,且单族缺失会改变语义:评估
StrictErrors=true。 - 需要依赖 StrictErrors:同时设置
PreferGo=true,并确认目标平台支持 Go 内置解析器。 - 只关心一个地址族:使用
LookupIP(ctx, "ip4", host)或"ip6"明确表达。 - 错误处理:使用
errors.As提取*net.DNSError,区分 timeout、temporary 与 not found。 - 重试:设置总预算、次数与抖动,禁止无上限循环。
- 观测:记录错误类别、地址族数量和耗时,按数据分级要求保护内部域名与 DNS 地址。
- 测试:覆盖 A 成功/AAAA SERVFAIL、AAAA 成功/A 超时、单族无记录、上下文取消和搜索域临时错误。
常见问题
StrictErrors=true 是否要求 A 和 AAAA 都必须有记录?
不是。它针对的是多个子查询中的临时错误。一个地址族正常回答无记录,与超时、套接字错误或 SERVFAIL 不同。
为什么设置了 StrictErrors,却看不出行为变化?
可能当前使用了 cgo/系统解析器,也可能查询只有一个子任务,或失败未被标记为临时错误。先确认解析器模式和实际错误类型。
StrictErrors=true 会自动重试失败的 AAAA 查询吗?
不会。它改变的是部分结果处理策略,不是重试策略。重试次数和总超时仍应由应用层控制。
-
502 收藏
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
136 收藏
-
247 收藏
-
140 收藏
-
481 收藏
-
251 收藏
-
347 收藏
-
430 收藏
-
464 收藏
-
494 收藏
-
108 收藏
-
203 收藏
-
270 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习