想象这样一个周末:你第一次去朋友推荐的老王烧烤,掏出手机问路,绕了半小时才找到。第二次去,你轻车熟路,因为地址已经记在手机通讯录里了——这就是 DNS 缓存。但老王烧烤会搬家,万一换到街对面,你手机里存的旧地址就会让你白跑一趟。于是每张“地址便签”上都贴了一个有效期标签,写着“此地址最晚有效至几点几分”,这就是 TTL(Time To Live)。有效期太长,老板搬了家你还蒙在鼓里;有效期太短,你每次出门都要重新问路,累得慌。工程上所有 DNS 缓存与 TTL 的取舍,本质上都是在“别让用户多跑路”和“搬家后尽快通知大家”之间找平衡。这篇文章就用老王烧烤的故事,把缓存、TTL、负缓存、故障切换、全球加速和 ECS 这些概念串起来讲清楚。
对 AI 从业者来说,DNS 缓存与 TTL 是排查线上故障、做服务高可用和全球部署的必修课——很多“偶发超时”和“切了没生效”的根因,都藏在这张小小的有效期标签里。理解它,你就能从“背配置”升级为“懂架构”。
目录
可视化演示
通过DNS多级缓存层级查询(浏览器→OS→本地→根→TLD→权威)、TTL值权衡(命中率vs传播时间)与故障切换(主节点宕机→TTL过期→备用切换)三个模式,展示DNS缓存与TTL策略的核心机制。
本节配有交互动效(进入视口后自动加载,约 3KB)。
DNS多级缓存层级查询 浏览器→OS→本地解析器→根→TLD→权威逐级缓存减少查询延迟 每级缓存命中则直接返回未命中则向上一级查询直至权威 Browser→OS→Local→Root→TLD→Auth 多级缓存将平均延迟从50ms降至12ms
缓存基本概念:手机里的通讯录
上回说到,你第二次去老王烧烤,靠手机通讯录里的地址就能到。但仔细想想,这个“地址”其实不止存在你手机里——你记性不好,先翻手机备忘录;没记,再查通讯录;还没有,才打电话问总台客服。DNS 也是层层设卡:浏览器缓存、操作系统缓存、递归解析器缓存,最后才轮到权威服务器。每一层都是一本“通讯录”,谁先查到谁答复。
这些层并不神秘。你在电脑上访问一个网站,浏览器先看自己记没记过这个域名;没记,问操作系统;还没记,转发给运营商或云厂商的递归解析器;递归解析器如果也没有,才向全球的权威服务器要“官方答案”。所谓“缓存命中”,就是某一层直接翻到了通讯录条目,省掉了后面的漫长问路。
每个解析器都维护一张表,记录域名与 IP 的对应关系。命中与否直接影响体验:命中的延迟通常在几毫秒,而完整递归解析可能需要几十甚至上百毫秒。衡量缓存价值的关键指标就是命中率,它决定你的“通讯录”到底好用不好用。
层数越多,可以命中的“通讯录”就越多,但每本通讯录都有存旧地址的风险——于是有了 TTL 这个有效期标签,控制每层能“信多久”。下一节我们就来看这张便签是怎么倒计时的。
- 分子命中的查询数:在任意一层缓存直接找到答案的请求数,像你翻开通讯录就找到地址,不用打总台。
- 分母查询总数:统计周期内所有域名查询的次数,无论命中与否都算,是“问过多少次路”的总账。
- 乘以 100%把小数比例换成百分比,便于和监控告警阈值直接对比。
TTL工作机制:地址便签上的有效期
老王烧烤每张发出去的地址便签右上角,都印着“此地址有效至 14:30”。在你手机里躺着这段时间,你都不用重新问路;一过 14:30 便签作废,你再去就得重新问一次。这张便签的“有效期”就是 TTL——每个 DNS 记录上都有这么个数字,单位是秒。
TTL 由权威服务器的管理员设定。当你拿到一条 A 记录,它自带“存活时间”。缓存在本地后,解析器开始倒计时,时间归零就删掉这条记录,下次有查询时重新去找权威服务器拿新地址。所以 TTL 本质上回答了三个问题:这条记录能信多久?什么时候必须重新验证?别人改地址后,最迟多久能被你知道?
技术细节上,缓存条目里通常保存“写入时刻”,每次查询时用当前时间减去写入时刻,与 TTL 比较。过期就删除,绝不续期——这也是为什么 TTL 被称为“缓存一致性”的守门员。它不关心内容本身,只关心“这条信息还能不能继续用”。
现在你明白了:TTL 是写死的一张期限,解析器只是机械地倒计时。但问题是,这张期限印多长才合适?这就引出了工程里最常见的权衡题。
- TTL权威服务器给这条记录规定的总寿命,单位秒,相当于便签上“有效至 14:30”那一刻。
- 当前时间减缓存写入时间这条记录已经在缓存里躺了多久,是“已经消化掉的时间”。
- 相减取差总寿命减去已存活时间,得到还能信多久;差为 0 或负数就删掉重查。
TTL权衡取舍:保质期长与短的博弈
老王烧烤的地址便签,印得时间长短各有讲究。印 24 小时?客人今天不用再问路,但明天老王搬到街对面,便签还在指着老地址;印 10 分钟?再稳准也不怕,可每个人每 10 分钟都要给总台打一次电话,总台电话直接被轰炸。
工程里的权衡一模一样。记录 TTL 设得长,缓存命中率高、权威服务器负载低、用户平均延迟好看——代价是变更传播慢;设得短,故障切换和 IP 调整能被快速感知——代价是查询量上升、权威压力变大、抖动被放大。
怎么选?经验法则是“动态记录短一点,静态记录长一点”。例如 API 网关、调度域名用 30~300 秒;图片、静态资源这类很少变的域名用 3600~86400 秒。变更前还会先调低 TTL,“预热”整个缓存链,再实施切换,避免一刀切后在切换瞬间造成全网查询风暴。
所以别问“TTL 最佳值是多少”,要问“老王多久可能搬家、客人多久问一次路”——场景决定答案。可别忘了,还有一种缓存连“不存在”都要记,那就是负缓存。
- TTL被缓存记录的最长寿命,是“全体客户端感知更新”的最大等待时间来源。
- δ(德尔塔)各递归解析器轮询权威服务器的琐碎时间差,通常远小于 TTL,但理论存在。
- 近似相加给出“从改 DNS 到最后一个用户拿到新地址”的经验上界,用于预估切换窗口。
负缓存机制:不存在也是一种情报
有天你路过老王烧烤原址,发现店没了,问总台,客服说“该店已关闭”。你会把“这家店不存在”也记在脑子里,至少这周不再特意跑一趟。DNS 也一样:查询一个不存在的域名,权威返回 NXDOMAIN,而这个“不存在”也可以被缓存——这就是负缓存。
没有负缓存时,攻击者或误配置的客户端反复查询不存在的域名,每次请求都会穿透各级缓存,直达权威服务器,造成资源消耗甚至 DDoS 放大。有了负缓存,递归解析器在一段时间内直接回答“没有”,上游压力骤减,恶意查询的放大效应被拦在门外。
负缓存能存多久由权威服务器的 SOA 记录决定。RFC 2308 给出了计算规则:取 SOA MINIMUM 字段与 SOA 记录自身 TTL 中较小的一个。设太短,防不住反复轰炸;设太长,域名恢复上线后,别人要等很久才能“发现它又活了”。
“不存在”也是宝贵的情报,记下来,既省力气又防轰炸。但真正考验 TTL 功力的时候,是老王真的搬家那一刻——故障切换。
- SOA MINIMUM权威服务器在 SOA 记录里声明的“建议负缓存时长”,单位秒,相当于总台说“这家店至少关一周别来问”。
- SOA 记录 TTL承载 SOA 记录本身的存活时间,是两个期限里的另一个候选。
- 取较小者负缓存时长取两者较小的,防止“不存在”被记太久,耽误新店重新开业被发现。
故障切换策略:搬家前的告示
老王烧烤真的搬家了,搬到街对面。问题是,每位客人手机通讯录里都留着旧地址,纸条上还印着“24 小时有效”。整整一天,客人络绎不绝地扑空。老王吸取教训,每到要搬家,就提前在门口贴出“地址有效期仅 10 分钟”的告示。
线上服务故障切换完全同理。当你把流量从故障的 IP-A 切到 IP-B,凡是缓存了“域名→IP-A”的解析器,都要等 TTL 倒计时结束才会重新查询。TTL 越大,“扑空”的时间越久。所以在计划变更前,工程团队会先调低 TTL,等整个缓存链“冷却”,再执行切换。
成熟的切换流程是:提前数小时把 TTL 从 3600 调到 60;等待至少一个原 TTL 周期让旧缓存自然过期;执行 IP 切换;监控观察稳定;再把 TTL 调回正常值。多 IP 场景下,健康检查会自动把故障 IP 从 A 记录里摘除,但摘除的“见效速度”同样由 TTL 决定。
把 TTL 当成运维的“刹车灯”——变更前一脚点刹,切换后才能稳稳停下,而不是让全员追尾。老王的故事还没完,他后来开了全国分店,这时候调度又成了新难题。
- max_i(TTL_i)所有相关记录里最大的那个 TTL,代表最不肯刷新的一本通讯录。
- ε(伊普西龙)客户端侧缓存生效的微小误差,通常可忽略,但工程上要留余量。
- 求和最坏情况下,全量客户端拿到新地址所需的时间上界。
全球加速配置:就近吃烧烤
老王烧烤做大了,全国开了分店。顾客打电话问总台“老王烧烤在哪”,总台不会只报总店地址,而是看你在北京就报北京分店,在杭州就报杭州分店。这就是全局负载均衡,而 DNS 的响应就是那台“总台电话”。
CDN 和 GSLB 依赖 DNS 做就近调度:权威服务器根据解析器查询的地理位置或网络位置,返回离用户最近的节点 IP。可是如果这条记录 TTL 太长,用户长时间“钉”在旧节点上,新增节点、节点故障都难以及时反映,就像你明明搬到杭州,通讯录还记着北京分店。
因此 CDN 场景的 TTL 通常是分钟级(60~600 秒),在“少问路”和“调度及时”之间取折中。有些 CDN 甚至用很短的 TTL 配合 ECS 高频精确调度。工程上还会结合 TTL 与监控缓存命中率动态调整,让“就近”真正意义上贴着网络拓扑走。
想清楚“分店在哪、客人多远”,你才能定下让大家都少跑路的有效期。但总台还有个尴尬:它只知道你的电话号码归属地,不知道你此刻人在哪——ECS 就是来解决这个的。
- S可选 CDN 节点集合,像老王在全国开的各家分店,用户吃哪家都行。
- RTT_j用户到第 j 个节点的往返时延,衡量“跑过去要多久”。
- 取最小值调度器返回让用户最快到店的那个节点,体验最好。
ECS精准调度:告诉总台你在哪
总台电话还有个难题:你的手机号归属地是北京,但你现在人在上海出差,总台按号码归属地推了北京分店——跑错地方了。于是你得多说一句“我人在上海”,总台才报上海分店。DNS 里的这句话就是 ECS(EDNS Client Subnet)。
传统 DNS 查询到达权威服务器时,权威只能看到递归解析器的 IP,无法精确到每个用户。ECS 在请求里额外携带客户端 IP 前缀,让权威/CDN 能做更精细的地域调度。代价是:缓存键从“域名”变成“域名+子网”,缓存被拆分,命中率下降;同时客户端 IP 前缀的暴露带来隐私考量。
工程实践中,ECS 一般按 /24 前缀携带,只对需要地域精细调度的域名启用,并在权威侧控制缓存分组大小。打开 ECS 后要重点监控权威服务器的缓存条目数和解析成功率,防止缓存碎片化拖垮性能。安全团队还要评估 IP 前缀暴露面,必要时做匿名截断。
告诉总台“你在哪”能吃到最近的分店,但也因此让总台要记更多小纸条——精准和成本永远是配对的。从缓存到 TTL,从负缓存到 ECS,老王烧烤的故事其实就是整个 DNS 工程哲学:一切都在为“让用户少跑路”和“让信息不落伍”之间找到那个刚刚好的平衡点。
- 域名记录数需要精细地域调度的 DNS 记录数量,是需要“按城市配菜单”的菜品数。
- 客户端子网数因为携带了 IP 前缀,同一域名按不同网段拆出的缓存份数,像按城市拆分的分店库存。
- 相乘两者相乘得到缓存条目总量,直观看出 ECS 带来的缓存压力来源。
AI 应用场景
场景1:大促前的故障切换演练
阶段:在线服务发布与回滚保障
解决的问题:传统做法是直接在 DNS 后台改解析,结果用户继续访问旧 IP 数小时,线上“扑空”投诉一片。AI 时代的在线服务同样依赖 DNS 做入口调度,掌握“先降 TTL 再切换”的方法后,可以把故障切换的感知时间从小时级压缩到分钟级。
具体案例:某电商平台大促前发现核心机房网络抖动,SRE 团队提前 6 小时把 API 域名的 TTL 从 3600 秒降到 60 秒,等旧缓存自然过期后切换流量,再观察 30 分钟稳定后调回 600 秒,全程用户无感。
场景2:CDN 全球加速调优
阶段:在线推理服务的全球体验优化
解决的问题:大模型推理服务面向全球用户,如果 DNS 调度不精准,海外用户被迫访问跨洋节点,首字延迟飙升。合理配置 TTL 与 ECS,可以让不同地区的用户命中最近的服务节点。
具体案例:某 AI 公司为海外用户部署多地域推理集群,将入口域名的 TTL 设为 120 秒并开启 ECS,权威服务器按客户端子网返回最近节点,海外平均首包延迟下降了 40%。
场景3:防 DNS 反查轰炸
阶段:线上服务安全防护
解决的问题:攻击者常对不存在的域名发起高频查询形成 DDoS 放大。如果没有负缓存,每次查询都穿透到权威服务器;配置好负缓存后,递归层直接拦截,权威压力骤降。
具体案例:某 SaaS 平台遭遇对随机子域名的查询轰炸,安全团队在权威服务器上调优 SOA MINIMUM 至 600 秒,递归解析器大量命中负缓存,权威服务器查询量下降 90%。
Python 演示
import time class TTLDNSCache: def __init__(self): self.records = {} self.negative = {} self.hits = 0 self.queries = 0 def lookup(self, domain, authoritative, ttl, neg_ttl): now = time.time() self.queries += 1 if domain in self.records: ip, expire = self.records[domain] if now < expire: self.hits += 1 return ip del self.records[domain] if domain in self.negative: expire = self.negative[domain] if now < expire: self.hits += 1 return None del self.negative[domain] if domain in authoritative: ip = authoritative[domain] self.records[domain] = (ip, now + ttl) return ip self.negative[domain] = now + neg_ttl return None def simulate(authoritative, ttl, seconds=600, step=3): cache = TTLDNSCache() t = 0.0 domains = list(authoritative.keys()) + ["ghost.shop.com"] while t < seconds: for d in domains: cache.lookup(d, authoritative, ttl, neg_ttl=30) t += step return cache def main(): records = { "www.shop.com": "10.0.0.1", "api.shop.com": "10.0.1.1" } for ttl in [60, 300, 3600]: c = simulate(records, ttl) print("TTL=%5ds 命中率=%5.1f%% 总查询=%d" % ( ttl, 100.0 * c.hits / c.queries, c.queries)) if __name__ == "__main__": main()
class TTLDNSCache:定义一个迷你 DNS 缓存,内部维护正缓存records和负缓存negative,就像手机里存地址便签和“这家店不存在”的备忘录。if now < expire:检查记录是否仍在 TTL 有效期内,没过期就直接命中,省掉一次“问路”。del self.records[domain]记录过期就删掉,下次查询会重新向权威请求,这是 TTL 倒计时的核心动作。self.negative[domain] = now + neg_ttl查询不存在的域名时,把“不存在”也缓存一段时间,对应负缓存机制。for ttl in [60, 300, 3600]:分别用短、中、长三种 TTL 跑同一段流量,对比输出结果。100.0 * c.hits / c.queries计算缓存命中率,能看到 TTL 越长、命中率越高的趋势。
常见误区
误区TTL 设成一天,缓存命中率高,权威服务器也轻松,为什么不直接拉满?反正缓存是好事。
正解TTL 大意味着所有缓存都要等很久才刷新。一旦老王烧烤搬家(IP 变更或故障切换),用户可能几小时都访问旧地址。TTL 是“更新时效”与“查询成本”之间的权衡,不存在绝对最优,只有“合不合适”。
为何常见:只看到缓存带来的性能收益,忽略了分布式系统里“变更传播延迟 ≈ TTL”这个本质。
误区在 DNS 管理后台改了 IP,以为全世界马上就能访问新地址,结果同事反馈还是连不上。
正解权威服务器是改了,但每层缓存还握着旧记录,最长要等一个完整 TTL 周期才过期重查。所以规范流程是“先降 TTL → 等待冷却 → 再切换”,而不是改完就完事。
为何常见:把“权威数据已更新”和“全网数据已更新”混为一谈,忽略了缓存中间层的存在。
误区只要配置好正常域名就行,不存在的域名没人访问,负缓存设不设无所谓。
正解攻击流量常打向不存在的域名(随机子域名轰炸),若没有负缓存,每次请求都穿透到权威服务器,形成 DDoS 放大。负缓存相当于“此路不通”的告示牌,能挡住绝大多数无效流量。
为何常见:看不到“不存在”也是一种高价值缓存信息,低估了重复查询的放大成本。
误区ECS 能精准调度,那就给所有域名都开启,性能一定更好。
正解ECS 会按子网拆分缓存,缓存命中率下降、压力上升,还会暴露客户端 IP 前缀。只对真正需要地域调度的域名(如 CDN、GSLB 入口)启用,才是工程上的最佳实践。
为何常见:把“精准调度”当作免费午餐,忽略了缓存按子网分割带来的碎片化代价。
面试题
Q1. 首次访问 www.example.com 时,正确的 DNS 查询顺序是?
- A.浏览器缓存 → 操作系统缓存 → 递归解析器 → 权威服务器← 正确答案
- B.权威服务器 → 递归解析器 → 浏览器缓存
- C.操作系统缓存 → 权威服务器 → 浏览器缓存
- D.递归解析器 → 权威服务器 → 操作系统缓存
答案:A。DNS 查询从离自己最近的缓存开始逐级向上:浏览器、操作系统、递归解析器,最后才问权威服务器。这就像你找餐厅先翻手机通讯录,找不到才打总台,最后才问老板本人。其他选项都颠倒了“从近到远”的顺序。
Q2. TTL 的单位和含义是什么?
- A.字节,表示缓存记录占用的空间大小
- B.秒,表示缓存记录的有效期← 正确答案
- C.毫秒,表示网络请求的超时时间
- D.次,表示一条记录最多被转发几次
答案:B。TTL(Time To Live)的单位是秒,表示一条 DNS 记录能在缓存里存活多久,过期后必须重新向权威服务器查询。它就像地址便签上的“有效至几点”标签,单位绝不是字节、毫秒或转发次数。
Q3. 某 A 记录的 TTL=60 秒,管理员修改 IP 后,理论上最迟约多久全网感知到变更?
- A.立即生效
- B.约 60 秒← 正确答案
- C.至少 60 分钟
- D.与 TTL 完全无关
答案:B。最“顽固”的缓存最多保留旧记录 60 秒,之后过期重查就拿到新 IP,所以全网感知的最迟时间约等于 TTL。这就像老王搬家的告示上写“地址有效期还剩 60 秒”,所有客人最多再等一分钟就会知道新店址。选项 A 忽略了缓存,C 夸大了周期,D 与 TTL 定义矛盾。
Q4. 以下哪些场景适合设置较长的 TTL?
- A.很少变更的静态资源域名← 正确答案
- B.频繁故障切换的 API 域名
- C.公司官网首页这类稳定记录← 正确答案
- D.需要分钟级动态调度的 GSLB 域名
答案:AC。静态资源、官网首页这类记录很少变化,长 TTL 能大幅提升命中率、降低权威压力。而频繁故障切换的 API 和分钟级调度的 GSLB 域名需要快速响应变更,应该用短 TTL。选项 B、D 违背了“变更越频繁,TTL 越要短”的原则。
Q5. 负缓存的主要作用是什么?
- A.缓存错误的解析结果以加速访问
- B.缓存 NXDOMAIN 响应,拦截对不存在域名的重复查询← 正确答案
- C.缓存反向解析记录
- D.减少 HTTPS 握手的次数
答案:B。负缓存把“这个域名不存在”也缓存起来,避免每次都穿透到权威服务器,能有效抵御针对随机域名的 DDoS 轰炸。它不是缓存错误答案,也不是反向解析或 HTTPS 优化手段。就像你记住“这家店关了”就不会反复跑冤枉路。
Q6. 按 RFC 2308,负缓存的时长如何计算?
- A.恒等于 300 秒
- B.等于 SOA 记录的 TTL
- C.取 SOA MINIMUM 与 SOA 记录 TTL 中较小者← 正确答案
- D.等于最近一次正缓存记录的 TTL
答案:C。RFC 2308 规定负缓存时长取 SOA MINIMUM 字段与 SOA 记录自身 TTL 的较小值,这样既能防轰炸,又不会让“不存在的域名”太久不被重新发现。A 是拍脑袋值,B 只取了一个因子,D 把正缓存和负缓存混为一谈。
Q7. ECS(EDNS Client Subnet)在 DNS 请求中额外携带了什么?
- A.客户端 IP 前缀← 正确答案
- B.权威服务器的公钥
- C.递归解析器的主机名
- D.客户端的查询次数
答案:A。ECS 在 DNS 请求里携带客户端 IP 前缀(通常 /24),让权威服务器或 CDN 能按用户地域做精细化调度,而不是只看递归解析器的位置。这就像你打电话给总台时多说一句“我在上海”,好让对方推荐上海分店。
Q8. 启用 ECS 后可能带来哪些代价?
- A.缓存按子网分割,命中率下降← 正确答案
- B.暴露客户端 IP 前缀,带来隐私风险← 正确答案
- C.必然导致域名解析失败
- D.权威服务器缓存条目数上升← 正确答案
答案:ABD。ECS 让缓存键从“域名”变成“域名+子网”,缓存被拆碎、命中率下降,权威侧条目数也成倍上升;同时暴露 IP 前缀确实有隐私顾虑。但它不会必然导致解析失败,C 过于绝对。就像分店越精细,总台要记的小纸条越多,还暴露了你所在的城市。
Q9. 负缓存能帮助缓解哪类问题?
- A.DNS DDoS 放大攻击← 正确答案
- B.缓存投毒攻击
- C.密码暴力破解
- D.中间人攻击
答案:A。攻击者常对不存在的域名发起高频查询,负缓存让递归层直接返回“不存在”,避免请求穿透到权威服务器,从而缓解 DDoS 放大。缓存投毒、密码爆破、中间人攻击都与负缓存的拦截机制无关。
Q10. 故障切换前对 TTL 的合理操作是哪些?
- A.提前调低 TTL,让整个缓存链“冷却”← 正确答案
- B.等待一个原 TTL 周期后执行切换← 正确答案
- C.切换瞬间把 TTL 拉到最大
- D.全程不碰 TTL,直接改 IP
答案:AB。标准流程是先调低 TTL 让旧缓存自然过期,再执行切换,这样能最大限度缩短“扑空”窗口;切换稳定后再把 TTL 调回正常值。C 和 D 都会让旧地址在缓存里存活更久,导致故障恢复被严重拖延。