系统设计 / DNS · 层级、委任、缓存,以及不存在的「浸透」 / 「浸透」这个说法 待审核 9 / 11
TTL · 否定缓存 · 迁移

「浸透」这个说法

改一条 DNS 记录之后各处不会立刻看到新值,这件事是真的。用来描述它的那个说法——「DNS 浸透」「等待扩散」「等它生效」——把机制讲反了,而讲反的代价是实际的:它让人在该排查异常时选择等待。

1 · 没有任何东西在扩散

回顾前面几页:权威服务器改了数据之后不通知任何人,只是坐等被问;递归服务器按 TTL 各自到期,到期后主动重新解析一遍。全部动作都是 pull。

唯一带 push 味道的机制是 zone transfer 里的 NOTIFY(第 3 讲),而它只是一个提示:不携带数据、不被信任,收到后 secondary 仍要自己去查 SOA、自己比 serial、自己发起传送。它作用的范围也只在同一个 zone 的各台权威服务器之间,与递归服务器的缓存无关。

所以「修改在向外扩散、正在浸透到各地」这个图景在协议里找不到对应物。实际发生的是相反方向的事:各处的缓存各自独立地过期,然后各自回来问。

2 · 要等多久:一个确定的答案

既然是各自到期,那么「要等多久」就完全由改动之前那条记录的 TTL 决定。

定理 2.1 设某条记录改动前的 TTL 为 TT。若各递归服务器都正确实现 TTL,则改动后至多 TT 秒,所有缓存都不再持有旧值。

证明 任一持有旧值的缓存,其缓存动作必然发生在改动之前,故其到期时刻至多为「改动时刻 + T+\ T」。最不利的情形是恰在改动前一瞬间缓存,此时到期时刻趋于「改动时刻 + T+\ T」。∎

图 2-1 · 三台起始时刻不同的递归服务器,在改动后各自何时看到新值。可拖动时间与调整 TTL 观察最坏情形恰好是一个 TTL。

注意上界由旧 TTL 决定,不由新写的 TTL 决定——把 TTL 从 86400 改成 60 这个动作本身,要等 86400 秒才在各处生效。这直接给出一条操作次序:先调短 TTL,等一个旧 TTL 过完,再改内容。

各级的 TTL 通常差着两三个数量级,于是过期是分层发生的。第 3 讲末节记的那处实测正是这件事:本系列 fixture 里 dev 的委任 TTL 是 172800 秒、vega.dev 的是 10800 秒、主机记录是 3600 秒,主机记录过期时委任还剩得多,于是重新解析只回退到 dev 那一级,不回到根。

3 · 新增记录要看否定缓存

上面那条只覆盖「改」与「删」。新增一个此前不存在的名字,等待时间由另一个数决定:否定缓存的 TTL。

若在记录建好之前有人查过这个名字,各处缓存里存的是「此名不存在」,而这条否定的寿命是 min(SOA 的 MINIMUM, SOA 记录自身的 TTL)(第 3 讲)。两个数都得看——只改 MINIMUM 而 SOA 的 TTL 更小时,实际生效的是后者。

警示 · 这解释了一个常见的错觉:「我明明还没建记录就先查了一下,建好之后反而更慢」。确实如此——那一次查询把否定应答种进了缓存。所以配置新域名时,不要在记录建好之前反复查它;要验证请直接问权威服务器(dig @<权威服务器> <名字>),绕开所有缓存。

4 · 等超过一个 TTL 仍是旧值:该查什么

按 §2,超过一个旧 TTL 还看到旧值就不是缓存的正常行为,继续等没有意义。可能的原因按排查顺序:

权威服务器上还没改。托管服务商的管理面板写入与权威服务器实际生效之间常有一段延迟,而这段延迟是那家服务商的实现,不是 DNS 的规定。直接 dig 各台权威服务器,逐台核对。

各台权威服务器应答不一致。zone transfer 没跑通,或 serial 没递增(第 3 讲),于是一部分 secondary 还在给旧数据。这时症状会呈现为「时好时坏」,取决于解析器随机挑到了哪一台。

实际问到的不是设想中的那台解析器。中间有转发器(路由器、公司网关、容器里的 dnsmasq),或者浏览器走了 DoH 绕开系统解析(第 8 讲)。浏览器自身还有一层 DNS 缓存,与系统的那层独立。

记录改在了不生效的位置。落在委任点之下被遮蔽(第 2 讲)、CNAME 与其他类型共存、或者改的是子 zone 顶点那份 NS 而实际生效的是父 zone 那份。

DNSSEC 断链。签名过期或 DS 对不上时,做验证的解析器给 SERVFAIL(第 7 讲)。这一档的表现不是「旧值」而是「什么都没有」,容易被误报成「还没浸透」。

在这些原因里等待都不会让事情好转,其中几种还会随时间恶化。

5 · 权威服务器迁移

换一批权威服务器是最容易出问题的一类改动,因为它同时牵动三处:父 zone 的委任、子 zone 顶点的 NS、以及 DNSSEC 的 DS。

图 5-1 · 迁移的五步与各步之间必须等待的原因。可逐步查看,每一步下方是跳过或做错它的后果。

五步之间没有一处可以并行,而每一处「要等」都是在等某个 TTL 过完。第 1 步尤其要提前:调短 TTL 这件事本身要等一个旧 TTL 才生效,迁移当天才开始调就已经晚了一个 TTL。

注 · 第 1 步要调的是父 zone / 委任元那一份 NS 的 TTL。写本系列的模拟器时踩到过这一点:递归服务器缓存的委任来自委任应答,而委任应答里的 NS 出自父 zone,TTL 也是父 zone 那一份——本系列 fixture 里子 zone 顶点的 NS 写 600 而父 zone 写 10800,缓存里存的是 10800。只把子 zone 顶点那份调小,缓存里该待多久还待多久,因为在只走过委任的解析器那里,缓存里用的一直是父那份。

同时迁移多样东西是另一个常见错误。注册商转移(registrar transfer)与权威服务器迁移应当分开做——不同的服务商在这件事上的配合程度差别很大,而 RFC 8078 与相关运维文档把「非协作的 DNS 运营方」列为一类需要专门处理的情形。Web、邮件等服务的迁移同理:应当在第 1 步之前就在新址上开始服务,在第 5 步之后才在旧址上停止,让服务的可用区间完整覆盖 DNS 切换的整个窗口。

6 · 说法本身

「浸透」这个词之所以招人反对,不是因为用词不雅,而是因为它承载了一个错误的因果模型,并且这个模型会导出错误的行动。相信「修改正在扩散」的人会等;而 §4 列出的每一种真实原因,等待都不解决,其中几种还会恶化。

比较准确的替代说法是「等缓存到期」(TTL 到期)——它指出了等待的对象是一个确定的时长,也指出了这个时长是可以事先控制的。

7 · 参考文献

  1. Andrews, M. (1998). Negative Caching of DNS Queries (DNS NCACHE) (RFC 2308). IETF.
  2. Barr, D. (1996). Common DNS Operational and Configuration Errors (RFC 1912). IETF.
  3. Gudmundsson, O., & Huque, S. (2017). Managing DS Records from the Parent via CDS/CDNSKEY (RFC 8078). IETF.(含「非协作 DNS 运营方」情形下的换钥与迁移。)
  4. Elz, R., & Bush, R. (1997). Clarifications to the DNS Specification (RFC 2181). IETF. §5.4 说明父 zone 与子 zone 各自那份 NS 的地位差别。
  5. 鈴木常彦. (2011). 浸透言うな!. e-ontap.com. (同一套误解在日文语境里的原始批评,本页论证与它同源;原站点已不可访问。)
  6. 大谷亘. DNS の基礎の基礎. 慶應義塾大学 SFC. 本地副本,附录二 「浸透言うな」って?(p68–76)。本页是本节的改写:§4 的排查清单对应 p71,§2 与 §3 的 操作次序对应 p72,§5 的迁移五步与「不要同时迁移多样东西」对应 p73–74,条目 5 那篇批评 也是经它转引。p71 另列了两项本页未展开的实现级原因——幽灵域名脆弱性,以及权威与缓存 共存服务器的实现不备。