DNS 缓存 从 TTL 到否定响应的作用范围
从一个具体请求或状态变化出发,追踪数据流与方法成立的条件。
章节速览7 节 · 展开选择
网络基础把 DNS 放在一次网页请求的开头,HTTP 缓存讨论网页表示能否复用。它们都叫缓存,却没有保存同一种东西:DNS 常保存名称对应的资源记录,HTTP 缓存保存请求所选择的表示及其元数据。
这次只追问两件事:一条 DNS 结果还能用多久,它究竟回答了哪些查询? 把时间和作用范围分开,就不容易把“没有 AAAA 记录”“名称不存在”和“解析服务器暂时没给出结果”混成一种失败。
先固定一条结果,而不是模拟完整解析器
本文假定响应已经完成来源、问题匹配等必要校验,只讨论 IN 类、普通 ASCII 完整主机名的一份有效结果。正结果是一组名称、类型、类相同且 TTL 一致的资源记录(RRset);例子只用 A、AAAA、MX、TXT。名称末尾的点表示完整名称,字母大小写不构成本文比较中的区别。
下面不处理别名链、委派查找、DNSSEC 证明、子树推断、抢先刷新、过期数据服务或缓存容量淘汰。内部程序只是单条结果的时间与匹配核算,没有读取或修改本机 DNS 设置,没有向真实解析器发送测试查询,也没有实现 DNS 报文解析。
RFC 1034 §3.6解释资源记录的名称、类型、类与 TTL;RFC 2181 §5.2澄清同一 RRset 的 TTL 应一致。这里直接以已符合条件的一组记录作为起点,不把拼接不同来源的记录当作合法 RRset。
TTL 是复用上限,不是整个名称的更新时间
假设解析器在逻辑时刻 r=100 秒收到一份可缓存的 A 记录集,收到的 TTL 为 T=60 秒。若未提前淘汰、未取得更新结果,也不使用过期复用机制,则:
到期时刻 d=r+T=160;在时刻 t 的剩余寿命为 max(0,d−t)。
本文允许缓存复用的区间为 r≤t<d;t=d 时已经到期。
时间都使用同一条按整秒递增的逻辑时间轴。公式描述“收到以后还剩多久”,并不表示每层缓存都能重新获得原始权威 TTL。RFC 1035 §3.2.1规定 TTL 为零的结果只能用于当前事务,不能靠缓存继续复用;本文的缓存查询也不会把它当作新事务的命中。
| 条目与收到时间 | 收到的寿命 | 查询时刻 | 剩余秒数 | 本文缓存复用判断 |
|---|---|---|---|---|
| A 记录;r=100 | 60 | 100 | 60 | 可复用 |
| 同一 A 记录 | 60 | 150 | 10 | 可复用,向下游转发时不能仍报 60 |
| 同一 A 记录 | 60 | 160 | 0 | 恰好到期,不再作为新事务的缓存命中 |
| 否定结果;r=200 | min(300,120)=120 | 250 | 70 | 仅在该否定结果的查询范围内复用 |
| 同一否定结果 | 120 | 320 | 0 | 恰好到期 |
TTL 不是“这个域名所有数据同时更新”的周期。A 与 AAAA 是不同类型的记录集,可能有不同 TTL、不同到达时间;本地程序、递归解析器和其他中间层也可能在不同时间收到自己的副本。
若上游在 t=150 向下游回答这份结果,忽略本例未建模的传输耗时,下游收到的剩余 TTL 应是 10。下游到期时刻仍为 150+10=160。反过来,每次命中都重新设置 60 秒,会把到期点错误推到 210;频繁查询甚至可能让旧数据一直续命。
这个不变性来自一个简单等式:在转发时刻 u,剩余量为 d−u;下游的新到期点为 u+(d−u)=d。它解释的是同一份缓存信息转发时的倒计时;收到一份新的、经过校验的响应,是另外一个更新事件。
两种否定:名称不存在,还是这一类数据没有
先看两个自拟的查询结果,名称均不经过 CNAME:
| 查询及响应 | 能表达的结论 | 同名换类型时能否直接沿用这个否定 |
|---|---|---|
missing.example. IN A,RCODE 为 NXDOMAIN |
该类中的这个名称不存在 | 本文同一 QNAME、QCLASS 的查询可以复用,不依赖 QTYPE |
api.example. IN AAAA,NOERROR 且确认为 NODATA |
没有所问的 AAAA 记录集 | 不能据此否定同名 A 或 MX;需要包含 QTYPE |
| 同一查询得到 SERVFAIL 或超时 | 解析过程没有得到可用结论 | 不能改写成“名称不存在” |
NODATA 不是一个独立的 DNS RCODE。它需要从完整响应内容判断;空 Answer 加 NOERROR 也不自动就是可缓存的 NODATA,例如还可能遇到委派等情况。本文从已经确认的否定结果起步,不提供仅凭两个字段分类报文的捷径。
RFC 2308 §5给出这两种否定缓存的匹配范围:NXDOMAIN 对应 QNAME 与 QCLASS,NODATA 还要包括 QTYPE。这样,查询 api.example. 的 AAAA 失败,并不会把同名的 A 正结果一同判成不存在。
这里刻意只比较同一个名称。后来的 RFC 8020 §2规定 NXDOMAIN cut:缓存的名称不存在结论还可影响该节点的后代查询,并列出继续使用已有未过期数据等例外。本文内部模型不实现这套子树推断;它对另一个名称返回未命中,不代表真实解析器必须对后代重新查询,也不把这个模型当作完整的现行否定缓存规则。
自检:AAAA 返回 NODATA,为什么不能直接回答“这个名称没有 IPv4 地址”?
AAAA 与 A 是不同的 QTYPE。NODATA 的匹配键包括类型,只能复用“这个类、这个名称的 AAAA 记录集缺失”这一结论;A 记录仍须由自己的结果回答。
否定 TTL 从 SOA 取得,也要不断倒数
否定响应没有所问的正记录可附上寿命,因此通常借助 Authority 中的 SOA。按 RFC 2308 §3,权威服务器设置否定响应中 SOA 的 TTL 时,取 SOA 自身 TTL 与 SOA.MINIMUM 的较小值。
本例 SOA 自身 TTL 为 300,MINIMUM 为 120,否定缓存寿命为 min(300,120)=120 秒。
在 r=200 收到时,到期点为 320;t=250 剩 70,t=320 不能继续命中。若接收的是另一个缓存转发来的否定响应,还要尊重它已经减少的 SOA TTL,不能重新拿到权威处的完整窗口。RFC 2308 §6要求缓存回答携带相应 SOA,并减少已经经过的时间。
没有 SOA 的否定响应,不能从空气中编造一个无限寿命。RFC 2308 建议不缓存这类否定;本文的内部模型直接不建立可复用条目。TTL 为零也不建立这种条目。真实解析器还可以施加更短的本地上限,因此 TTL 是许可复用的上限,不是必须保留到最后一秒的承诺。
不要把 SOA.MINIMUM 机械地理解为“全区所有记录当前 TTL 的下限”。其历史语义经过更新;也不要从 RFC 1035 的早期说明推断 SOA 永远不能缓存。RFC 2181 §7.2 与 §8澄清了 SOA TTL 与 TTL 上限的相关约定。
flowchart TD
A[已校验并确认的查询结果] --> B{结果类别}
B -->|正记录集| C[保存名称 类型 类与收到的TTL]
B -->|NXDOMAIN或NODATA| D{带有可用SOA}
D -->|是| E[取SOA TTL与MINIMUM较小值]
D -->|否| F[本模型不建立可复用否定条目]
C --> G[计算到期时刻]
E --> G
G --> H{查询范围匹配且尚未到期}
H -->|是| I[按剩余TTL回答]
H -->|否| J[缓存不能回答本次查询]
B -->|解析失败| K[走独立故障缓存与重试规则]
解析失败缓存,不能冒充名称不存在
SERVFAIL、拒绝、超时等情况,没有提供“这个数据存在还是不存在”的有效结论。它们仍可能需要缓存,以免故障触发大量重复查询;但缓存的含义是暂时抑制无益重试,而不是证明 NXDOMAIN。
RFC 9520 §3.2已经要求解析器缓存解析失败,时间至少 1 秒、至多 5 分钟,并说明不同失败条件可以采用不同的匹配方式。不能把 RFC 2308 早期的“某些故障缓存可选”原样当作当前结论。
本文内部核算刻意不实现这套故障策略,也不把 SOA 的否定寿命套给它。对反复失败的实际系统,应另查解析器实现、超时重试与容量约束;不能根据这个教学模型安排生产故障恢复。
DNS、HTTP 与 LRU,各自回答不同的问题
| 机制 | 保存或管理什么 | 首先要检查什么 |
|---|---|---|
| 本篇 DNS 结果缓存 | 资源记录集或有范围的否定结论 | 查询名称、类型、类与剩余 TTL |
| HTTP 缓存 | 被请求选中的表示、验证器与指令 | 变体匹配、新鲜度及条件验证 |
| LRU 淘汰 | 有限容量中条目的保留次序 | 访问历史与复用距离 |
LRU 可以让一个尚未到期的条目因容量不足而离开;反过来,条目即使仍在内存里,TTL 到期后也不能按本文规则直接复用。DNS 重新查询成功,也不会自动使已有 HTTP 表示变新鲜;这些时间线要分别记录。
flowchart LR A[名称解析问题] --> B[先查DNS问题与剩余TTL] C[网页表示复用问题] --> D[先查HTTP指令和年龄] E[容量不足问题] --> F[再查淘汰策略与访问历史] B --> G[分别记录事件 不能共用一个过期时间] D --> G F --> G
手算之后,核对什么边界
内部验证使用独立的整秒倒计时与事件区间参考,核对到期前、恰好到期、TTL 零、多层转发不续命,以及同名跨类型、不同名称和大小写规范化。模型只接收已规范化为 0 至 2³¹−1 的整数 TTL;RFC 2181 §8 对线缆上高位被置为 1 的 TTL 所作处理不在这个输入接口里重新实现。名称验证限定普通 ASCII 主机名,不能当作 DNS 对任意标签的完整语法要求;不解析 RDATA,不执行报文合法性或服务器符合性认证。
tests/verify-dns-cache-article.cjs 已验证 1000 个随机条目的 95541 个倒计时位置、同数目的时间平移、8000 组名称与类型范围、2000 次转发链、24 个大整数边界和 29 类非法输入;手算表五行与图中五项坐标也接受核对。两张 SVG 共 30 个文字节点边界通过,原图已逐张查看。以上都是教学算术和模型边界检查,没有运行真实解析器性能实验。
| 现象或输入 | 本文的处理与限制 |
|---|---|
| 时间早于条目收到时刻 | 拒绝这条矛盾的时间线 |
| TTL 到零、没有 SOA 的否定 | 不作为新事务的缓存命中 |
| 同名 AAAA 缺失、A 有结果 | 两种类型的结论分别匹配 |
| 名称大小写或尾点不同 | 本模型要求完整尾点,ASCII 字母统一小写比较 |
| SOA TTL 与 MINIMUM 不同 | 取较小值;转发时仍扣除已经经过的时间 |
| TTL 很大或收到时刻相加超出安全整数 | 拒绝教学模型不能准确表示的输入 |
| 只有 SERVFAIL 或超时 | 不伪造成 NXDOMAIN;故障策略另按 RFC 9520 处理 |
自检:每次命中都把寿命重新设成 60 秒,会发生什么?
同一份信息的到期点会被反复向后推,频繁访问可能让旧信息一直留存。例子里150时只剩10秒,下游也应在160到期;重新给60秒则错误延长到210。取得新的已校验响应才是另一个更新事件。
资料核对于 2026-10-27:RFC 1034、RFC 1035、RFC 2181、RFC 2308、RFC 8020、RFC 9520。正文时间线与图中名称均为原创教学例子,不是线上测量。