浏览器访问一个域名时,应用通常只发出一次 DNS 查询,但这不等于某台服务器“一次查完全部答案”。在常见架构中,本机的存根解析器把问题交给递归解析器;递归解析器先查缓存,未命中时再从根、顶级域到权威服务器逐级追踪委派关系。
理解这条链路,才能解释许多看似矛盾的现象:记录已经修改,为什么有些用户仍看到旧地址?域名明明存在,为什么查询某种类型却没有答案?为什么 dig +trace 正常,业务使用的 DNS 却返回 SERVFAIL?本文从协议机制出发,给出可复现的观察方法和分层排障流程。
一、先分清四个角色
一次典型查询涉及四类角色:
| 角色 | 职责 | 常见位置 |
|---|---|---|
| 存根解析器(stub resolver) | 接收应用请求并转交给配置的 DNS 服务器 | 操作系统、容器运行时 |
| 递归解析器 | 代客户端寻找最终答案,并维护缓存 | 企业 DNS、运营商 DNS、公共 DNS |
| 根与 TLD 服务器 | 返回下一层权威服务器的委派信息 | 根区、.com、.cn 等 |
| 权威服务器 | 对自己管理的区域给出权威答案 | 域名托管商或自建 DNS |
RFC 1034 区分了递归与迭代:递归服务要么返回最终答案,要么返回错误,不把委派直接丢给普通客户端;迭代查询则允许服务器返回“下一步去问谁”的 referral。递归解析器正是通过多次迭代查询完成客户端委托。
以查询 www.example.com A 为例,缓存全部未命中时,路径可抽象为:
应用
-> 存根解析器
-> 递归解析器
-> 根服务器:去问 .com
-> .com 服务器:去问 example.com 的权威服务器
-> 权威服务器:返回 www.example.com 的 A 记录
<- 缓存结果并把答案返回应用
委派响应通常在 Authority 区给出 NS 记录;必要时,Additional 区还会附带 glue 地址,避免为了寻找某个名字服务器的地址而形成解析循环。若目标是 CNAME,解析器还要继续解析其目标名称,因此别把“查到 CNAME”误认为已经获得可连接的 IP。
二、缓存的核心不是“有或没有”,而是剩余 TTL
资源记录中的 TTL 以秒为单位,表示记录可以在缓存中保留多久。递归解析器返回缓存答案时,客户端看到的通常是递减后的剩余 TTL,而不是权威区文件里的初始值。
可以连续观察:
dig @1.1.1.1 example.com A +noall +answer
sleep 10
dig @1.1.1.1 example.com A +noall +answer
如果两次命中同一缓存节点,第二次 TTL 通常会减少约 10 秒;若中途刷新、负载均衡到另一节点或记录被预取,数值也可能跳变。TTL 归零后表示该缓存副本不应继续使用,但并不意味着全世界会在同一时刻刷新:不同递归解析器的首次查询时间不同,各自的过期时刻也不同。
因此,变更记录时应把传播过程理解为“旧缓存逐个过期”,而不是权威服务器主动推送。工程上可以在计划变更前至少一个旧 TTL 周期先降低 TTL,等待旧缓存自然淘汰后再切换地址;完成并稳定运行后再恢复 TTL。临时改低 TTL 无法让已经缓存的高 TTL 记录提前失效。
缓存还可能存在于多个层次:
应用内部 -> 操作系统 -> 本地缓存服务 -> 企业/运营商递归解析器
只清理浏览器缓存不一定有效;反过来,公共递归解析器已更新,也不代表长连接、连接池或应用自己的地址缓存已经更新。排障时必须明确每次查询实际经过哪一层。
三、负缓存:不存在的答案也会被记住
DNS 不只缓存成功答案。RFC 2308 定义了负缓存,并区分两个常被混淆的结果:
- NXDOMAIN:查询的名称不存在;
- NODATA:名称存在,但没有所查询类型的记录。它不是单独的 DNS RCODE,需要根据响应内容判断。
例如,某名称有 A 记录但没有 AAAA 记录,查询 AAAA 可能得到 NODATA,而不是 NXDOMAIN。把两者混为一谈,会误导监控和故障处理。
权威服务器返回负答案时,会在 Authority 区携带区域 SOA。按照 RFC 2308,负缓存 TTL 取 SOA 记录自身 TTL 与 SOA.MINIMUM 字段中的较小值,并像普通缓存 TTL 一样递减;到零后该负答案不能继续使用。这解释了一个常见故障:先查询了尚未创建的名称,递归解析器缓存 NXDOMAIN,随后即使权威端补上记录,部分客户端仍要等负缓存过期。
观察负答案时不要只看 Answer 区:
dig @1.1.1.1 does-not-exist.example.com A
重点检查:
status是NXDOMAIN、NOERROR还是SERVFAIL;- Authority 区是否有 SOA;
- SOA 的剩余 TTL 是否递减;
- 同名查询其他类型时结果是否不同。
SERVFAIL 与 NXDOMAIN 含义不同。前者表示服务器未能完成处理,可能来自上游超时、DNSSEC 验证失败、委派错误或权威服务器不可达;不要把它解释成“域名不存在”,也不要用创建一条记录来盲目修复。
四、用 dig 分层观察,而不是只换一个公共 DNS
1. 查询系统实际使用的递归解析器
dig example.com A
输出中的 SERVER 表明请求发给了谁;标志位中的 rd 表示客户端请求递归,ra 表示服务器声明可提供递归。注意,ra 是能力声明,不代表这一次一定完成了完整递归过程,答案也可能直接来自缓存。
2. 对比不同递归解析器
dig @1.1.1.1 example.com A +noall +answer +comments
dig @8.8.8.8 example.com A +noall +answer +comments
若答案或剩余 TTL 不同,先考虑缓存时间点、地理调度和 EDNS Client Subnet 等差异,不要立刻断定某个解析器“错误”。
3. 直接询问权威服务器
先找权威 NS,再向其中一台发非递归查询:
dig example.com NS +short
dig @a.iana-servers.net example.com A +norecurse
ISC BIND 的 dig 文档说明,+norecurse 会清除查询中的 RD 位。直接比较权威答案和递归答案,可以判断问题在权威配置还是缓存层。若多个权威节点答案不一致,应检查区文件发布、序列号和节点同步,而不是等待递归缓存自行“修好”。
4. 用 trace 重放委派链
dig +trace example.com A
+trace 从根服务器开始按委派向下查询,并自动关闭递归请求。它适合发现缺失 glue、错误 NS、某层委派不一致等问题。但它绕开了业务正在使用的递归解析器,因此“trace 成功”只能说明从当前主机进行迭代追踪大体可行,不能证明企业 DNS 的缓存、转发器或 DNSSEC 验证链没有故障。
5. 必要时强制 TCP
dig +tcp example.com A
当响应被截断、UDP 路径受限或怀疑防火墙只放行了一种传输时,对比 UDP 与 TCP 很有价值。不要沿用“DNS 永远只用 UDP 53”的排障假设;生产网络应同时验证所需的 UDP/TCP 53 路径。
五、一个可执行的排障顺序
遇到“域名解析异常”,可以按以下顺序收敛范围:
- 记录现场:保存查询名、类型、时间、客户端网络、
SERVER、状态码、flags 和完整响应; - 确认名称与类型:A、AAAA、CNAME、TXT 是不同问题,NXDOMAIN 与 NODATA 也不同;
- 比较递归与权威:权威正确而递归旧,多半是缓存;权威节点之间不同,则先修权威发布;
- 检查 TTL:同时查看正答案和 SOA 驱动的负缓存,不要只看控制台显示的配置值;
- 追踪委派:用
+trace检查根到权威的链路,并核对父区 NS/glue 与子区实际服务; - 比较网络与传输:更换客户端网络,分别测试 UDP/TCP,检查防火墙、NAT 和 MTU;
- 最后再清缓存:清理动作要针对真正缓存旧结果的层,且应保留清理前证据。
一个实用的证据包可以这样采集:
name=example.com
{
date -u
dig "$name" A
dig "$name" AAAA
dig "$name" NS
dig +trace "$name" A
dig +tcp "$name" A
} > dns-debug.txt
生产环境还应把实际递归服务器地址、权威节点查询结果和变更时间线加入证据包。这样即使故障已经因 TTL 到期而消失,也能回溯当时究竟是旧缓存、负缓存、委派还是网络路径问题。
六、工程设计建议
- TTL 是一致性与查询成本的权衡:低 TTL 缩短变更窗口,但会增加权威查询量,并不能替代健康检查与故障转移设计;
- 先设计回滚再改 DNS:保留旧服务一段时间,避免旧缓存用户立即失去服务;
- 监控要覆盖多层:既从公共递归解析器查询,也直接检查每个权威节点;
- 不要依赖单一记录类型:双栈环境同时监控 A 与 AAAA,防止 IPv4 正常而 IPv6 路径故障;
- 区分控制面与数据面:托管控制台显示“已保存”只证明配置已提交,不等于所有权威节点、递归缓存和客户端都已收敛。
总结
DNS 故障的关键不是记住更多刷新缓存命令,而是建立分层模型:存根解析器把请求交给递归解析器,递归解析器通过 referral 逐级找到权威答案,再按 TTL 缓存;不存在的名称或类型也可能通过 SOA 形成负缓存。排障时把权威、递归、客户端缓存和网络传输逐层拆开,并结合 status、flags、Authority 区与剩余 TTL 取证,通常能比“反复切换 DNS”更快定位根因。