net.LookupIP适合简单场景,net.Resolver用于指定DNS服务器、超时控制及查MX/TXT等记录;HTTP客户端需通过DialContext注入自定义解析。

Go 里做 DNS 查询,net.LookupIP 是最常用也最省心的起点,但它只查 A/AAAA、不支持超时、不指定 DNS 服务器——真要可控,必须切到 net.Resolver;手动拼包或用 miekg/dns 属于特定场景,不是日常首选。
什么时候该用 net.LookupIP,什么时候必须换 net.Resolver
net.LookupIP 适合快速验证、本地开发、或对解析行为无定制要求的场景。它直接走系统 resolver(/etc/resolv.conf 或 Windows 系统 API),自动处理双栈、重试和基本超时(由系统控制,Go 不暴露参数)。
但以下情况必须用 net.Resolver:
- 需要指定 DNS 服务器,比如强制走
1.1.1.1:53或内网 CoreDNS - 必须设查询超时(
context.WithTimeout),避免阻塞 goroutine - 要查 MX/TXT/CNAME 等非 A 记录,得调
net.LookupMX等函数,它们底层都依赖net.Resolver实例 - Kubernetes Headless Service 场景下想拿到全部 Pod IP,得显式调
resolver.LookupIP(ctx, "ip", domain),且不能用"ip4"否则漏 IPv6 地址
别误以为 net.LookupIP 能传带端口的域名——它只接受纯域名,如 "example.com",传 "example.com:443" 会报 *net.DNSError,Err 字段是 "no such host",不是格式错误。
net.Resolver 怎么设 DNS 服务器和超时
核心就两个字段:PreferGo 和 Dial。设 PreferGo: true 强制走 Go 自带 DNS 解析器(不读系统配置),再通过 Dial 控制连接目标:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
-
Dial是个函数,不是字符串地址;正确写法是返回net.Conn,例如用net.Dialer{}.DialContext连 UDP - UDP 查
8.8.8.8:用"udp"网络 +"8.8.8.8:53"地址;TCP 则换"tcp",但注意某些公共 DNS(如 Cloudflare)不响应 TCP 查询 - 必须用
context.WithTimeout包裹LookupIP调用,否则Dial超时无效;net.Resolver本身不管理上下文超时 - 别漏掉错误判断:
err可能是*net.DNSError,需检查.IsNotFound或.IsTemporary字段区分“不存在”和“临时失败”
示例关键片段:
resolver := &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, addr string) (net.Conn, error) {
return (&net.Dialer{Timeout: 2 * time.Second}).DialContext(ctx, "udp", "1.1.1.1:53")
},
}
ips, err := resolver.LookupIP(context.Background(), "ip", "github.com")
查 MX、CNAME 等记录的坑点
net.LookupMX、net.LookupTXT、net.LookupCNAME 都是独立函数,不共享参数或返回结构,且行为差异大:
-
net.LookupMX返回[]*net.MX,其中MX.Host是域名,不是 IP,得再查一次LookupIP -
net.LookupCNAME不返回 CNAME 链,只返回最终 canonical name;若目标无 CNAME 记录,它会 fallback 到 A 记录并返回空字符串 +no CNAME record错误——这不是异常,是正常信号,别当失败处理 -
net.LookupSRV的Service参数不含下划线,比如查_https._tcp.example.com,传的是"https"和"tcp",不是"_https" - 所有这些函数默认仍走系统 resolver;要指定 DNS 服务器,得自己 new 一个
net.Resolver实例,再调它的LookupMX方法(如resolver.LookupMX(ctx, "example.com"))
HTTP 客户端怎么用自定义 DNS 解析
http.Client 完全不认 net.Resolver,必须通过 http.Transport.DialContext 注入:
- 别只设旧版
Dial字段,TLS 握手时仍会走系统 DNS;必须用DialContext -
DialContext里先调resolver.LookupHost(ctx, host)拿域名对应的所有 IP,再逐个尝试拨号 - 别假设
LookupHost返回的 hostname 就是最终目标——它可能返回 CNAME,而你真正要连的是那个 CNAME 指向的地址,所以最好用LookupIP直接拿 IP - 并发拨多个 IP 时,注意
net.Dialer.Timeout和ctx.Done()的配合,避免 goroutine 泄漏
最容易被忽略的是:Kubernetes 中访问 Headless Service 时,net.Resolver 默认不保证返回全部 A 记录,且 Go 1.19+ 对 SRV/A 解析更严格,升级后可能突然只返回一个 IP——这时得确认 CoreDNS 配置是否开启 endpoint_pod_names,并确保查询域名是完整 FQDN(含 .svc.cluster.local)。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










