net.LookupHost 解析失败时返回空切片和非 nil error,须同时检查 err != nil 和 len(ips) == 0;它不区分 IPv4/IPv6,推荐用 LookupIP 或 LookupIPv4;DNS 配置、超时、并发限流及缓存需手动处理。

net.LookupHost 解析失败返回空切片或 error
Go 的 net.LookupHost 在 DNS 查询失败时不会 panic,而是返回 []string{}(空切片)加一个非 nil 的 error。很多人只检查切片长度,忽略 error,结果误以为“解析成功但没 IP”,其实根本没查通。
- 必须同时判断
err != nil和len(ips) == 0—— 前者是网络/配置问题(如 /etc/resolv.conf 错误、DNS 不可达),后者才可能是域名真没 A 记录 - 本地 hosts 文件生效,
net.LookupHost会读取它;但某些容器环境(如 Alpine + musl)可能跳过 hosts,优先走 DNS,行为不一致 - 超时由底层 net.DefaultResolver 控制,默认无显式 timeout,可能卡住几秒;生产环境建议用自定义
net.Resolver配Timeout
想拿到 IPv4 和 IPv6 分开的列表,别只用 LookupHost
net.LookupHost 只返回主机名对应的所有 IP 字符串(不含协议族信息),无法区分是 IPv4 还是 IPv6,也不保证顺序。如果需要分类处理或强制只取 IPv4,得换函数。
- 用
net.LookupIP:返回[]net.IP,可遍历用ip.To4() != nil判断 IPv4 - 若只要 IPv4,更直接的是
net.LookupIPv4(Go 1.22+),返回[]*net.IPAddr,避免手动过滤 -
net.LookupHost对www.example.com和example.com可能返回不同结果——CNAME 链路不同,别假设等价
在 Docker 或 CI 环境里解析总是超时或失败
不是代码问题,大概率是运行环境 DNS 配置异常。Go 默认用系统 resolv.conf,但容器常覆盖它为 127.0.0.11(Docker 内置 DNS),而某些精简镜像(如 distroless)压根没这个文件。
- 先确认:
cat /etc/resolv.conf是否存在、内容是否合理(避开127.0.0.1这类不可达地址) - 临时调试可硬编码 resolver:
resolver := &net.Resolver{PreferGo: true, Dial: func(ctx context.Context, network, addr string) (net.Conn, error) { return net.DialContext(ctx, network, "8.8.8.8:53") }} - CI 中用 GitHub Actions 时,
ubuntu-latest默认 DNS 可靠,但自建 runner 若走公司内网 DNS,需确认其支持递归查询
并发调用 LookupHost 没做限制,导致 DNS 请求堆积
net.LookupHost 底层发 UDP 包,无连接复用,每次都是独立 DNS 查询。高并发下容易触发本机端口耗尽(尤其短连接服务),或被上游 DNS 限流(如 Cloudflare 对单 IP QPS 限制)。
立即学习“go语言免费学习笔记(深入)”;
- 不要裸写
for range urls { go func() { net.LookupHost(...) }() }—— 必须加 goroutine 限速(如 semaphore)或用带缓存的客户端(如github.com/miekg/dns自建 query pool) - 简单场景可用
sync.Map缓存结果,key 为域名,value 为[]string+time.Time(TTL 参考值 30s 起,实际看响应中的 TTL 字段) - 注意:Go 的默认 resolver 不自动遵循 DNS 响应里的 TTL,缓存逻辑得自己实现
真正麻烦的不是写不对函数,而是 DNS 查询混在业务逻辑里,失败时看不出是网络问题、配置问题,还是目标域名本身有问题。多打一行 fmt.Printf("lookup %s: %v, %v\n", host, ips, err) 能省掉大半排查时间。


















