一次迁移把域名 A 记录从旧机房指向新机房,改完之后一部分用户访问新站,一部分还是旧站,持续了整整两天。第三天有人把 TTL 从 3600 调到 60,说"这样以后就好办了"——但已经发出去的那批 3600 秒记录,还在各地递归服务器上缓存着。
DNS 变更是少数"改了之后不听你指挥"的操作:你控制的是权威记录,用户用的是缓存副本。中间隔着运营商的递归服务器、终端系统的缓存、以及应用自己实现的缓存,每一层都按自己的规则保留一段时间。
1. 一条记录的传播路径
| 层级 | 缓存依据 | 谁能控制 |
|---|---|---|
| 浏览器/系统 | 各系统自己的策略,可能忽略 TTL | 终端用户 |
| 本地递归服务器(运营商) | TTL,且常设最小值下限 | 运营商 |
| 公共 DNS | TTL 与自身策略 | 服务商 |
| 权威服务器 | 无缓存,直接返回 | 你 |
最后一列是全部事实:只有权威服务器在你说了算的范围内。前三层的保留时间由"TTL"与"对端是否遵守 TTL"共同决定。
判断一次解析结果来自哪一层,有个很直接的命令:dig 的输出里有一个"已缓存时长"字段,它告诉你这条记录在这台服务器上存在了多久。
// 看缓存来源与已缓存时长(第二个数是 TTL 剩余,第三个是原始 TTL)
dig www.example.com A +noall +answer
// www.example.com. 2841 IN A 203.0.113.10
// 剩余 2841 秒,若原始 TTL 是 3600,说明这条记录已被缓存了 759 秒
这解释了一个常见困惑:TTL 设成 60 秒,为什么还有用户访问旧站两小时。因为部分递归服务器会对短 TTL 设下限(比如不低于 300 秒),而某些终端会忽略 TTL 直接缓存到进程重启。
2. 变更前必须做的一件事:降 TTL
正确顺序不是"改完再降 TTL",而是先降 TTL、等一个旧 TTL 周期、再改记录。
| 步骤 | 时间点 | 目的 |
|---|---|---|
| 1. 把 TTL 降到 60 | T-24 小时 | 让旧缓存尽快过期 |
| 2. 等待旧 TTL 走完 | T-24 到 T | 确保各地缓存里的都是短 TTL 记录 |
| 3. 修改记录指向 | T | 此时传播窗口是分钟级 |
| 4. 观察流量切换 | T 之后数小时 | 确认新站承接 |
| 5. 恢复原 TTL | T+24 小时之后 | 减少权威服务器压力 |
第二步是关键,也最常被跳过:在还没降 TTL 的时候就改了记录,等于用旧的、可能是几小时的 TTL 在传播这次变更。降 TTL 的动作本身也要经过这个传播周期才生效,所以它必须提前做。
// 变更前核对:各公共 DNS 看到的 TTL 是否已经降下来
for r in 8.8.8.8 1.1.1.1 223.5.5.5 114.114.114.114; do
echo -n "$r "
dig +noall +answer @$r www.example.com A | awk '{print $2"s", $5}'
done
// 第二列是当前缓存剩余 TTL;若仍是 3600,说明降 TTL 尚未传播完,不要动记录
3. 为什么会出现"一半新一半旧"
| 原因 | 机制 | 表现 |
|---|---|---|
| 缓存未过期 | 各地 TTL 起算时间不同 | 分批切换,持续数小时 |
| 多记录并存 | A 记录有多条,客户端轮询 | 新旧混合,看起来随机 |
| 双份配置 | CDN 与源站各有一套解析 | 部分路径指向旧站 |
| 应用层缓存 | 服务端缓存了解析结果 | 即使 DNS 变了,连接仍连旧的 |
| 长连接未断 | 已建立的 TCP 连接不重新解析 | 老客户端持续连旧 IP |
还有一个更隐蔽的原因:客户端自己的解析缓存。很多语言的 HTTP 客户端会缓存解析结果直到进程重启,容器里的长驻进程尤其明显。这类缓存不受 TTL 约束,也不体现在任何 DNS 查询日志里。
第五项最容易被忽略:DNS 只影响新建连接。已经建立的长连接会一直用到断开为止,所以"改完 DNS 之后旧机器还有流量"是正常的,需要主动断开或等连接自然回收。
// 确认旧机器上的流量是"新连接"还是"长连接残留"
ss -tn state established '( dport = :80 or dport = :443 )' | wc -l
ss -tn state established '( dport = :80 or dport = :443 )' | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head
// 旧机房仍有大量 established → 长连接残留,考虑 graceful 关闭与连接迁移
4. 回退比变更更难
回退时最容易犯的错是"把记录改回去就完了"。但此时情况与变更前不同:新的 TTL 已经传播,旧的缓存可能还留着新指向。
| 阶段 | 变更时 | 回退时 |
|---|---|---|
| 缓存状态 | 全部指向旧值 | 新旧混杂 |
| 传播速度 | 受旧 TTL 限制 | 受当前 TTL 限制(通常更快) |
| 副作用 | 新站需承接流量 | 旧站需重新承接,且可能有数据已写入新站 |
| 关联变更 | 通常无 | 往往已做了一串关联改动 |
回退预案里还有一条常被忘记的:回退期间的旧 TTL 也要重新考虑。如果变更时把 TTL 从 3600 降到了 60,回退后应该保持 60 一段时间再恢复,否则恢复动作本身又会制造一轮不可控的传播。
第三行是回退的真正难点:DNS 回退只把流量带回去,已经写在新环境里的数据不会自己回去。所以回退预案必须包含数据处理,而不只是解析记录。
5. 可靠变更的四个配套
| 配套 | 作用 | 最小实现 |
|---|---|---|
| 变更前快照 | 记录改前的完整区文件与 TTL | 导出 zone 文件存档 |
| 灰度入口 | 先让一部分流量验证 | 权重解析或按地域解析 |
| 双写期 | 两个环境都能服务 | 旧站保留到观察期结束 |
| 观测口径 | 知道流量切换了多少 | 按来源 IP 段统计新旧站流量 |
这四项可以压成一份变更单,放在变更执行前逐项确认:
// DNS 变更单的六项,缺一项就不该执行
1. 当前 TTL 与降 TTL 的时间点(必须已传播完)
2. 改前 zone 快照路径(可回滚到精确状态)
3. 灰度方式(按权重 / 按地域 / 全量)
4. 新旧站同时可服务的确认(旧站保留到何时)
5. 观测口径(新旧流量各占多少,从哪看)
6. 回退触发条件与执行人(谁在什么指标下决定回退)
第六项的"执行人"要落到名字,且必须是变更窗口内在岗的人。
第五项"双写期"在网站迁移里常被简化成"旧站不关",但真正需要确认的是旧站能不能在切换后继续正确处理写入。如果旧站已经停止维护、或者它的数据不会被同步回主库,那么保留它只是在制造分叉。
第四项常被省略:切换过程中如果没有"新旧各占多少"的数字,就只能靠用户反馈判断。这个数字用两边的访问日志按时间聚合即可,不需要额外埋点。
6. 排错:五种"改了没生效"
| 现象 | 成因 | 处理方向 |
|---|---|---|
| 部分地区仍旧站 | 递归缓存未过期 | 核对各地 TTL 剩余时间 |
| 全部地区仍旧站 | 改的是错误区域/子域 | 用权威服务器直接查询验证 |
| 解析正确但访问失败 | 新站证书或端口未就绪 | 先验证直连 IP 与 SNI |
| 邮件或验证码异常 | 只改了 A 记录,MX/TXT 未同步 | 检查全部记录类型 |
| 恢复 TTL 后又乱了一次 | 恢复动作本身触发新一轮传播 | 恢复也要按变更流程做 |
第二项要用权威服务器直接查,绕过所有缓存:
// 绕过缓存,直接问权威服务器
dig +norecurse @ns1.example.com www.example.com A
// 若权威返回新值而递归返回旧值 → 纯缓存问题,等待即可
// 若权威仍是旧值 → 改动没生效,检查区域文件与推送
// 追踪完整解析链,看是哪一层给了旧答案
dig +trace www.example.com A | tail -20
// 变更前一次性导出全部记录类型,避免遗漏
dig +noall +answer example.com ANY
dig +noall +answer _dmarc.example.com TXT
dig +noall +answer example.com MX
dig +noall +answer example.com CAA
// 把输出存档;迁移后逐条比对,缺的补上
第四项值得单列:DNS 变更往往只想着 A 记录,而 MX、TXT(SPF/DKIM/域名验证)、CAA 这些记录在迁移中经常被遗漏,症状是"网站正常但邮件发不出去"或"某个第三方验证失效"。
7. 边界:什么情况可以简化流程
| 场景 | 为什么 | 做法 |
|---|---|---|
| 内部域名,无外部解析 | 只有自己控制递归 | 直接改,无需降 TTL |
| 无状态服务且可随时切回 | 回退成本低 | 保留旧站即可 |
| 已有全局流量管理(按权重) | 切换在应用层完成 | DNS 不动,改权重 |
| 测试域名、短生命周期 | 无真实用户 | 直接改 |
判断口径一句话:这次变更的回退是否需要搬数据。不需要,DNS 就是一个流量开关,流程可以简化;需要,它就是一个迁移项目,必须有观察期与数据处理方案。
8. 自测
- 四个层级里,哪一层是"你说了算"的?其余三层的保留时间由什么决定?
- 为什么必须先降 TTL 并等一个旧 TTL 周期,再改记录?跳过这一步会怎样?
- DNS 变更为什么只影响新建连接?旧机器仍有流量时,如何区分是缓存残留还是长连接残留?
- 回退与变更相比,多出哪两类难点?为什么"改回去就完了"不成立?
- 只改 A 记录会漏掉哪些记录类型?各自会导致什么症状?
- 判断一次 DNS 变更是否可以简化流程的关键问题是什么?