同一个 www.baidu.com,阿里 DNS 查是 3.8 毫秒,8.8.8.8 查是 175.5 毫秒,差了 45 倍以上。
更意外的是它们给的 IP 不是同一台:114 给的是 36.152.44.93,8.8.8.8 给的是 103.235.46.96。
我把整条解析链路拆成 6 跳做成一个单文件网页,从浏览器缓存一路查到权威服务器,每一跳的耗时都摆在同屏。

在线案例与源码:atomgit.com/deli007/dem... 成品只有一个 index.html,双击就能打开,页面里的数字来自 2026-10-16 的一次真实抓包。
一、为什么值得搞清楚:换 DNS 只快那一下
平时说换个 DNS 网速就快了,快的其实只有第一次解析那一下。
IP 一到手就按 TTL 缓存在本机,之后打开同一个网站根本不再问 DNS。
我在自己这台机器上量了两组数:缓存命中的查询 0.6 到 0.9 毫秒,没命中的冷查询 189 到 207 毫秒。
差了 200 多倍,但只差这一次。
所以换 DNS 能救的是「第一次打开慢」,救不了带宽,也救不了服务器本身慢。
更值得搞清楚的是另外一件事:这 6 跳里,你到底把域名交给了谁。
二、准备环境:进入码道 Web,把 6 跳需求写清楚
浏览器打开 devcloud.cn-north-4.huaweicloud.com/chat/home ,登录后在对话框里写需求,就能拿到一个能跑的单文件应用。
不用装东西,不用配环境,本文这个页面就是这么来的。
码道有三种使用方式:WebUI(浏览器对话)、TUI(终端命令行)和桌面 IDE(IDE 插件)。本文用 WebUI 版演示。
需求我写了 10 条,事后看最值钱的是这四条:
- 6 跳要按顺序高亮,第 4 跳必须把 .com 的 13 个 NS 名字和 glue IP 列出来;
- 真实抓包数据写进页面里的一个常量,不许自己编数字;
- 页面底部要有自检面板,12 条断言加载完自动跑,并把结果显示在页面上;
- 输入不存在的域名时第 6 跳标红,给出中文解释,上一轮成功的解析结果不许被清空。
第二条是这轮最省事的决定:数字原样进页面,写文章时不用另抄一遍。
三、一屏看清:6 个节点、一根耗时条、一张对比表
页面左边是链路,从上到下 6 个节点:浏览器 DNS 缓存、操作系统缓存与 hosts、本地 DNS 递归解析器、根域名服务器、顶级域服务器、权威域名服务器。

每个节点点开就是一跳的问答原文:问了什么、答了什么、原始应答码。
右边一根横条把 6 跳的毫秒数画出来,6 跳合计 329.7 毫秒,其中最慢的两跳是根服务器和顶级域服务器。
再往下是 4 台解析器的对比表,最底下那块自检面板加载完就给结果,我这次拿到的是 12/12 PASS。
我把 12 条挨着看了一遍,里面有三条是真会出错的:输入为空时开始按钮必须是灰的、TTL 倒计时每秒要减 1、点清空之后残留的动画状态必须归零。
四、验证:我自己把递归链路走了一遍
页面只是把链路画出来,数字得自己抓。我写了个几十行的脚本,直接用 UDP 把 DNS 报文发到 53 端口,自己解析应答。
第一跳和第三跳的差别最直观:hosts 文件是空的,本机 hosts 上一行自定义记录都没有。
本地 DNS 是 100.125.1.250,同一个域名问第二次只花 0.7 毫秒,返回的 TTL 从 288 一路递减到 26,说明这条记录是从缓存里拿的。
然后我跳过本地解析器,直接去问根服务器 198.41.0.4。
它给了我一份转介:.com 的 13 个 NS,a 到 m.gtld-servers.net,每条后面还带着 glue IP。
这份应答里有两个字段值得看:TC=1,表示 512 字节的 UDP 装不下 13 条 NS 加 glue,被截断了;RA=0,表示根服务器不做递归,它只告诉你下一步该问谁。
顺着转介里的 glue 地址问 192.5.6.30,也就是 a.gtld-servers.net,拿到的就是 baidu.com 的 5 个 NS:ns1、ns2、ns3、ns4、ns7。
再跳一跳问权威服务器 ns1.baidu.com,也就是 110.242.68.134,15.8 毫秒回来,AA=1,这是权威应答。

答案本身也有意思:www.baidu.com 先是一条 CNAME 指到 www.a.shifen.com,然后才给 A 记录。
| 跳 | 问谁 | 服务器 | 耗时 | 答了什么 |
|---|---|---|---|---|
| 1 | 浏览器 DNS 缓存 | 浏览器进程内 | 0 ms | 无记录,直接交给下一跳 |
| 2 | 系统缓存与 hosts | /etc/hosts 对应的 hosts 文件 |
0 ms | hosts 文件为空,未命中 |
| 3 | 本地 DNS | 100.125.1.250 | 0.7 ms | 缓存命中,TTL 288 递减到 26 |
| 4 | 根域名服务器 | 198.41.0.4 a.root-servers.net | 152.0 ms | .com 的 13 个 NS 加 glue,TC=1、RA=0 |
| 5 | 顶级域服务器 | 192.5.6.30 a.gtld-servers.net | 161.2 ms | baidu.com 的 5 个 NS 加 glue |
| 6 | 权威域名服务器 | 110.242.68.134 ns1.baidu.com | 15.8 ms | AA=1,CNAME 到 www.a.shifen.com,再给 A 记录 |
6 跳加起来 329.7 毫秒,这个数和我重复跑的几次都对得上。
根那条我连着问了三次,是 152.0、155.3、154.2 毫秒;顶级域两次是 161.2 和 158.8 毫秒。
五、4 台解析器:同一个域名,拿到的 IP 不是同一台
这张表是整篇文章里我最想让人看到的一格。
| DNS 服务器 | 归属 | 耗时 | 返回的 A 记录 |
|---|---|---|---|
| 223.5.5.5 | 阿里 DNS | 3.8 ms | 110.242.69.21 / 110.242.70.57 |
| 114.114.114.114 | 114 DNS | 23.0 ms | 36.152.44.93 / 36.152.44.132 |
| 100.125.1.250 | 本机配置的内网 DNS | 0.7 ms | 110.242.69.21 |
| 8.8.8.8 | Google DNS | 175.5 ms | 103.235.46.96 / 103.235.47.188 |
同为 www.baidu.com,114 给的是 36.152.44 网段,8.8.8.8 给的是 103.235.46 网段,两者不是同一台机器。
8.8.8.8 那条的 CNAME 链路还长一截:先到 www.a.shifen.com,再到 www.wshifen.com,最后才是 A 记录。
这就能解释一件常见的事:改完 DNS,有人觉得「网站变了个样子」,因为解析到的那台服务器确实换了。
耗时差距也真实存在,同一时刻 8.8.8.8 是 175.5 毫秒,本机内网 DNS 是 0.7 毫秒。
但别把这 45 倍套到整个网页上,它只发生在没命中的那一次查询上。
六、两个真 bug:一个是按钮不亮,一个把「拿不到」当成「命中缓存」
页面交回来我先跑了一遍流程,第二个 bug 才露出来。
第一个 bug 是跑完一轮之后按钮不亮。
复现很简单:打开页面,点开始解析,等状态变成「解析完成」,再点开始解析,点不动。
得先在输入框里敲一个字符,或者点一次清空,那个按钮才会重新变成可点的蓝色。
原因在流程函数里:它一进来就把开始按钮设成禁用,收尾时只把运行标记复位,没把按钮恢复回来。
修复是一行,在收尾处补一次按钮状态刷新。改完我连跑两轮,第二轮正常走到第 4 跳。
第二个 bug 更有意思,「浏览器实测」那块面板在说谎。
这个功能是点一下就用 fetch 请求一次 https://域名/favicon.ico ,再从浏览器的资源计时里把这次 DNS 查询的耗时读出来。
第一次点,面板上写着「本次命中了浏览器缓存,DNS 耗时为 0」。
可那次请求明明花了 23 毫秒,页面里也留着这条资源记录的地址和总时长。
我去看了原始条目:域名的查询耗时、连接耗时、协议、传输大小全是 0。
原因是跨域:对方响应里没有 Timing-Allow-Origin,浏览器就把这些细分字段一律置零,只留总时长。
代码把「字段等于 0」直接读成了「命中缓存」,于是把一次真实的解析说成了 0 毫秒。
修复是把这个判断拆成三种情况:有细分字段就照实显示;只有连接字段就说明复用了已有连接;全都没有就写明是跨域限制,并把总耗时补上去。

这两个 bug 有个共同点:页面自检的 12 条断言一条都没报,因为它们都是「发生在两次点击之间」的问题。
七、失败路径:域名不存在有两种,浏览器都报同一个错
输入一个不存在的域名,页面会在第 6 跳停住,节点标红,右侧写清「权威服务器返回 NXDOMAIN,浏览器报 ERR_NAME_NOT_RESOLVED」,上一轮成功的解析结果留在下面不清空。

页面这段是演示,我又去命令行把「不存在」真查了一遍,结果比演示细。
| 查询 | 本地 DNS | 114 | 223.5.5.5 | 8.8.8.8 |
|---|---|---|---|---|
| no-such-host-9f3a2.example.com | 195.6 ms,0 条记录加 SOA | 443.1 ms,同上 | 207.9 ms,同上 | 177.4 ms,同上 |
| nothing-9f3a2.invalid | 201.6 ms,应答码 3 | - | 65.7 ms,应答码 3 | - |
第一种是「这一段存在,但没有这条记录」,四台解析器都返回 0 条记录加一条 SOA,应答码是 0。
第二种是「整段都不存在」,应答码是 3,这才是严格的 NXDOMAIN。
浏览器对两种都报 ERR_NAME_NOT_RESOLVED,所以从用户视角感受不到区别,但在排查时要分清。
顺带一个反直觉的数据:查一个不存在的域名,114 那台花了 443.1 毫秒,比查到真实结果还慢。
八、实测数据汇总
| 项目 | 实测值 | 说明 |
|---|---|---|
| 成品体积 | 47454 字节 | 单个 HTML 文件,无外部依赖,无构建步骤 |
| 页面自检 | 12/12 PASS | 加载后自动跑,结果显示在页面底部 |
| 6 跳总耗时 | 329.7 ms | 0 + 0 + 0.7 + 152.0 + 161.2 + 15.8 |
| 缓存命中查询 | 0.6 到 0.9 ms | 同一个域名连续问本机 DNS |
| 未命中查询 | 189 到 207 ms | 五个随机子域各问一次 |
| 根服务器 | 152.0 到 155.3 ms | 三次实测,TC=1、RA=0,13 个 .com NS |
| 顶级域服务器 | 158.8 到 161.2 ms | 两次实测,返回 5 个 baidu.com NS |
| 权威服务器 | 15.8 ms | AA=1,CNAME 加 A 记录 |
| 解析器耗时差 | 3.8 ms 对 175.5 ms | 阿里 DNS 对 8.8.8.8,相差 45 倍以上 |
| 浏览器实测 | 23 ms | 跨域拿不到细分字段,只能读到总耗时 |
测这台机器是 Chrome 内核,出口是内网 DNS,换网络换时间数字都会变,只有量级可以参考。
九、本地复现
页面是单文件,双击就行,不需要联网也能看到 6 跳的完整动画和对比表。
起个静态服务也可以:python -m http.server 8000 ,然后访问 http://localhost:8000 。
想自己抓一遍,随便找个能发 UDP 的地方,往 53 端口发一条 A 查询的报文,再按格式拆应答,几十行就够。
想验缓存那一段,先问一次记录 TTL,隔几分钟再问一次,看 TTL 是不是少了几百秒。
想看浏览器的说法,按 F12 打开网络面板,点一次浏览器实测,再去看那条 favicon 请求的计时明细。
十、说点不足
失败路径是内置的演示,第 6 跳的权威服务器地址仍然沿用真实抓包里的 ns1.baidu.com,它不会真的去查你输入的那个不存在域名。
第一跳和第二跳的耗时在页面里记的是 0,这两跳在浏览器里拿不到可信的独立计时,我用的是「无记录、未命中」这个事实。
浏览器实测那块只能拿到总时长,跨域的限制绕不过去,除非对方站点自己开 Timing-Allow-Origin。
页面里的耗时全是 2026-10-16 那一次抓包的固定数据,不是实时查询,想看实时数字得自己跑脚本。
TTL 倒计时是演示用的,它从 288 开始每秒减 1,和真实缓存里那条记录同步不了。
十一、用码道做这个实验的体会
第一条是把「不许编数字」写进需求。我在需求里明确写了真实抓包数据写进一个常量,交回来的页面里就真的没有一处凭空生成的耗时。
第二条是让自检渲染到页面上。12 条断言写在页面里、结果显示在页面底部,我验收时不用再另开一个测试文件,眼睛扫一遍就知道哪条没过。
第三条和上面两个 bug 有关:自检查不出的东西,往往在两次点击之间。按钮会不会亮、第二次点击还灵不灵,这类问题得靠人真的连点两轮才看得见。
第四条是需求里要点名「拿不到字段时该怎么说」。页面第一版把跨域限制说成了命中缓存,就是因为需求里只写了要显示 DNS 耗时,没写取不到的时候怎么办。
第五条是数据要给出处。每条抓包记录都带 source 字段,写文章时我能把表格里的每一格倒回去对。
最后一句实话:这个页面是个教学工具,它不替你查 DNS,也不改你的 DNS 设置,看完它你得到的是判断力,不是加速器。