你在浏览器地址栏敲下
www.baidu.com,按下回车,网页瞬间打开。可在这几百毫秒里,你的电脑打了一连串"电话"------问邻居、问社区、问总部,最后才问到百度的"门牌号"。
这就是 DNS(Domain Name System) ,互联网的"电话簿"。
而你写的
InetAddress.getByName("www.baidu.com"),也在走同样的路。
大家好,我是 Evan ,一个用 dig +trace 亲眼看过 DNS 递归全程的 Java+AI 学生。
今天,我从你敲下回车开始,完整追踪一次 DNS 解析的"寻址漂流记":从本地缓存,到根 DNS,到顶级域,再到权威 DNS。最后用 dig +trace 亲手验证,并聊聊 Java 中 DNS 与 /etc/hosts 的优先级。

📌 写在前面
刚学计网时,老师说"DNS 是把域名转成 IP",我就觉得它像查字典。直到在智荟Agent中做多地域部署,发现线上某些域名解析偶尔会耗时 3-5 秒,查了才知道是 DNS 递归查询超时。后来我用了 dig +trace,才直观看到每一次查询都经历了十几层"问路"。
这篇博客,我就带你走一遍这条路。
一、DNS 解析的完整旅程(四步走)
当你输入 www.baidu.com,你的电脑按以下顺序"问路":

1.1 第一站:浏览器缓存(最快)
浏览器会缓存你最近访问过的域名 IP(时间很短,通常几分钟)。
在 Chrome 中输入 chrome://net-internals/#dns 可以看到。
1.2 第二站:操作系统缓存 + /etc/hosts
如果浏览器没有,就去问操作系统。
-
Windows:
ipconfig /displaydns -
Linux/macOS:
scutil --dns或查看/etc/hosts关键 :
/etc/hosts是"最高优先级",可以手动指定127.0.0.1 localhost,常用于本地开发。
1.3 第三站:本地 DNS 服务器(通常是你路由器或 ISP)
操作系统会向配置的 DNS 服务器(如 114.114.114.114、8.8.8.8 或公司内网 DNS)发起递归查询。
这个服务器大部分情况下已经缓存了常见域名的 IP,直接返回。如果缓存失效,它就要去"问根"。
1.4 第四站:根 DNS → 顶级域 DNS → 权威 DNS
如果本地 DNS 服务器没有缓存,它会替你去递归查询(这里有两种模式):


二、实验:用 dig +trace 亲眼见证
dig 是 Linux/macOS 上最强大的 DNS 调试工具。+trace 参数会模拟递归查询全过程。
bash
dig +trace www.baidu.com
输出片段解析:
bash
; <<>> DiG 9.10.6 <<>> +trace www.baidu.com
;; global options: +cmd
. 518400 IN NS a.root-servers.net.
. 518400 IN NS b.root-servers.net.
...
;; Received 228 bytes from 192.168.1.1#53(192.168.1.1) in 5 ms
- 第一段:从本地 DNS(
192.168.1.1)拿到 根 DNS 列表 (13 台,a~m.root-servers.net)。
bash
com. 172800 IN NS a.gtld-servers.net.
...
;; Received 839 bytes from 198.41.0.4#53(a.root-servers.net) in 30 ms
- 第二段:问根(这里选了
a.root-servers.net),根返回.com顶级域 DNS 地址。
bash
text
baidu.com. 172800 IN NS ns1.baidu.com.
baidu.com. 172800 IN NS ns2.baidu.com.
...
;; Received 205 bytes from 192.5.6.30#53(a.gtld-servers.net) in 25 ms
- 第三段:问
.com顶级域,返回baidu.com的权威 DNS(ns1.baidu.com 等)。
bash
text
www.baidu.com. 600 IN A 220.181.38.251
;; Received 84 bytes from 112.80.248.64#53(ns1.baidu.com) in 10 ms
- 第四段:问权威 DNS,最终拿到
www.baidu.com的 IP220.181.38.251。
每一步都有详细的时间和来源 IP,你可以清楚看到查询路径。
三、Java 开发中的 DNS 操作与陷阱
3.1 InetAddress.getByName() 的基础用法
java
InetAddress addr = InetAddress.getByName("www.baidu.com");
System.out.println(addr.getHostAddress()); // 220.181.38.251
这个调用会走操作系统配置的 DNS 解析流程(并会使用系统缓存)。
3.2 DNS 缓存:JVM 也有自己的缓存
JVM 会缓存 DNS 解析结果,默认 缓存成功结果(30 秒 ~ 无限) ,缓存失败结果(10 秒)。
可通过 JVM 参数调整:
bash
# 成功缓存时间(秒),-1 表示永不失效
-Dsun.net.inetaddr.ttl=60
# 失败缓存时间(秒)
-Dsun.net.inetaddr.negative.ttl=10
重要:如果你的服务依赖某个域名的 IP,但该 IP 会动态变化(如 CDN 或 Kubernetes 的 Service),请务必设置合理的 TTL,否则你可能一直拿到过期 IP。
3.3 /etc/hosts 在 Java 中的优先级
Java 的 InetAddress 会先读 /etc/hosts(或 Windows 的 C:\Windows\System32\drivers\etc\hosts),再走 DNS。
这常用于本地开发环境的域名映射,或绕过 DNS 故障。
3.4 DNS 超时与异常处理
java
try {
InetAddress addr = InetAddress.getByName("www.baidu.com");
} catch (UnknownHostException e) {
// 域名不存在或 DNS 解析失败
e.printStackTrace();
}
默认情况下,getByName 可能会阻塞很久(几十秒)如果 DNS 服务器无响应。
可以提前设置 sun.net.inetaddr.ttl 或使用异步解析库(如 Netty 的 DnsNameResolver)来避免阻塞主线程。
四、常见问题与优化

📝 总结

核心结论:
-
DNS 解析是分层的"问路"过程,每一层只负责自己管辖的部分。
-
dig +trace是透视整个流程的最强工具。 -
在 Java 中,
InetAddress.getByName()会走系统 DNS,但 JVM 有自己的缓存策略,需要根据业务场景调整 TTL。 -
线上服务应做好 DNS 超时处理,并考虑使用连接池,避免每次请求都重新解析域名。
🤔 思考题 :
你在生产环境部署一个 Java 微服务,它需要调用第三方 API(https://api.partner.com/v1/order)。某天,第三方突然更换了其 API 的 IP 地址(原 IP 已下线)。你的服务立刻开始报 UnknownHostException,但重启服务后就恢复正常。
问题 :为什么重启能恢复?如果不重启,有什么办法让服务"自动"感知到 DNS 变化?
(提示:考虑 JVM 的 DNS 缓存 TTL、sun.net.inetaddr.ttl,以及连接池的 DNS 刷新机制)
欢迎在评论区留下你的方案 ------ 下一篇我会聊聊 "NAT 与端口映射:你家路由器如何让所有设备共享一个公网 IP?"。