dns

08. DNS

0. 本章先解决什么问题

程序最终要用 IP 地址通信,但人和系统配置通常使用域名。

www.example.com -> 目标 IP

这一步看似简单,实际会影响请求的第一段延迟、流量调度、故障切换、缓存一致性和排障路径。

本章要解决:

  1. 域名是怎样被逐级解析成 IP 的。
  2. DNS 缓存和 TTL 为什么会让“改了配置但还没生效”成为正常现象。
  3. DNS 为什么既是命名系统,也是流量调度系统的一部分。
  4. 排障时怎样区分“解析失败”和“解析成功但连接失败”。

DNS 解析与缓存

这张图怎么读

DNS 的关键不是“域名变 IP”这么简单,而是分层查询和多级缓存。TTL 让旧答案在一段时间内继续存在,这就是域名变更不会瞬间全网生效的根本原因。

1. 域名是分层名字

域名从右到左层级越来越具体:

www.example.com.

部分含义
.根域
com顶级域
example二级域
www子域或主机名

最后的点表示根域,日常使用时通常省略。

分层设计的好处是权威可以逐级下放:

根域管理顶级域在哪里
顶级域管理 example.com 的权威服务器在哪里
example.com 的权威服务器管理 www.example.com 的记录

如果所有域名都由一个中心数据库保存,规模、性能和管理边界都会非常糟糕。DNS 的分层结构让全球命名系统可以分布式运行。

2. 解析过程:递归解析器帮你一路问

普通主机通常不会自己从根域一路问到底,而是把问题交给递归解析器。

简化流程:

DNS 递归与迭代解析链路

更具体一点:

  1. 应用请求解析 www.example.com
  2. 系统先查本地缓存/hosts 等本地来源
  3. 如果没有,询问配置的递归解析器
  4. 递归解析器如果没有缓存,询问根域
  5. 根域告诉它 com 的服务器在哪里
  6. com 告诉它 example.com 的权威服务器在哪里
  7. 权威服务器返回 www.example.com 的记录
  8. 递归解析器缓存结果并返回给客户端

解析器像一个代办员:客户端问一次,它负责把完整链路问完。

3. 递归查询和迭代查询

两个概念要区分:

查询方式谁继续问下一级常见位置
递归查询被问的一方负责给最终答案客户端问递归解析器
迭代查询被问的一方只告诉你下一步去问谁递归解析器问根/顶级域

客户端通常说:

请你直接给我最终答案。

根域、顶级域通常说:

我不知道最终答案,但你可以去问这个权威服务器。

这个分工让客户端简单,也让 DNS 能分层扩展。

4. 常见 DNS 记录

类型作用例子含义
A域名到 IPv4example.com -> 93.184.216.34
AAAA域名到 IPv6example.com -> 2001:db8::1
CNAME别名指向另一个域名www -> example.com
NS指定权威 DNS哪些服务器负责这个域
MX邮件服务器邮件投递到哪里
TXT文本记录验证、策略、元数据
SRV服务位置某服务运行在哪个主机和端口
PTR反向解析IP 到域名

4.1 CNAME 不是直接给 IP

CNAME 表示别名:

www.example.com -> site.example.net
site.example.net -> 203.0.113.10

解析器需要继续解析目标域名。CNAME 链太长会增加解析时间和失败点。

5. 缓存和 TTL:DNS 的效率与一致性取舍

DNS 查询如果每次都从根域问到权威服务器,性能会很差。缓存是 DNS 能扩展的关键。

缓存可能存在于:

应用运行时
操作系统
本机 DNS 缓存
递归解析器
浏览器或其他客户端
中间网络设备

TTL 表示记录可以缓存多久。

TTL 选择好处代价
短 TTL变更传播更快,故障切换更灵活查询更多,解析压力更高
长 TTL缓存稳定,查询少,延迟低变更生效慢,故障切换慢

所以“DNS 改了为什么还没生效”很多时候不是异常,而是旧缓存还没过期。

5.1 负缓存

解析失败也可能被缓存,这叫负缓存。

例如某个域名刚创建,之前有人查询时返回“不存在”,这个不存在结果可能也有缓存时间。于是你创建记录后,某些地方还会短时间继续认为它不存在。

边界条件:CNAME 链会放大缓存和失败点

CNAME 很方便,但它会让解析路径变长:

www.example.com
-> a.cdn.example.net
-> b.vendor.example.org
-> A/AAAA records

这条链里每一跳都有自己的 TTL、权威服务器和失败可能。排障时要看整条链,而不是只看最初的域名。

问题可能后果
CNAME 目标不存在原域名也解析失败
CNAME 链太长解析延迟增加,失败点增加
中间某一跳 TTL 很长切换不按预期生效
A 和 AAAA 结果差异IPv4/IPv6 用户表现不同
权威和递归看到结果不同缓存、委派或策略问题

所以“DNS 查一下有 IP”只是起点。真正要证明解析正确,需要把 CNAME 链、记录类型、TTL、权威答案和客户端实际答案一起看。

6. DNS 使用 UDP 还是 TCP

传统 DNS 查询通常使用 UDP,因为请求和响应小,适合快速查询。

但 DNS 也会使用 TCP,例如:

场景为什么可能用 TCP
响应过大UDP 放不下或被截断
区域传送DNS 服务器之间同步大量记录
某些安全/可靠场景需要更稳定传输

所以不要简单认为“DNS 只能走 UDP”。排障时 UDP 被拦截、响应过大、TCP fallback 失败,都可能造成解析异常。

7. DNS 和流量调度

DNS 不只把名字变成 IP,还常被用来做粗粒度流量调度。

一个域名可以返回多个 IP:

api.example.com -> 203.0.113.10
api.example.com -> 203.0.113.11
api.example.com -> 203.0.113.12

也可以根据地理位置、运营商、健康状态返回不同结果:

用户 A -> 离它更近的节点
用户 B -> 另一个区域的节点
故障节点 -> 暂时不返回

但 DNS 调度有天然限制:

限制解释
缓存存在客户端不一定马上拿到新结果
不是逐请求决策DNS 解析发生在请求前,不等于每个请求都重新选择
递归解析器代表多个用户权威 DNS 看到的可能是解析器位置,不是最终用户
故障摘除不一定即时旧结果可能仍在缓存中

因此生产系统往往把 DNS、负载均衡、健康检查、缓存节点一起使用,而不是只依赖 DNS。

8. DNS 失败和连接失败不是一回事

常见错误要分清:

现象可能含义
域名解析不到记录不存在、DNS 配置错、递归解析器不可达
解析很慢缓存未命中、DNS 服务器慢、网络丢包
解析到旧 IPTTL 缓存未过期、应用缓存
解析到多个 IP 但部分失败某些上游节点异常、路由不通
能解析但连不上TCP/UDP 连接问题,不是 DNS 本身
命令行能解析,程序不能程序运行环境 DNS 配置不同或内部缓存不同

一个请求失败时,DNS 只负责第一段:

域名 -> IP

之后还有:

IP 路由
传输层连接
TLS 握手
应用协议请求响应

9. 排障路径

排查 DNS 问题时,可以按这个顺序:

  1. 域名拼写是否正确。
  2. 本机 hosts 或本地配置是否覆盖。
  3. 当前环境使用哪个 DNS 解析器。
  4. 解析结果是否存在 A/AAAA/CNAME 等预期记录。
  5. TTL 是否导致旧结果仍然有效。
  6. 不同网络环境解析结果是否一致。
  7. 解析出的 IP 是否能建立连接。
  8. 目标程序和你手动测试是否在同一个运行环境。

不要只在宿主机手动解析一次就结束。程序可能运行在容器、虚拟机、沙箱、不同网络命名空间或不同配置环境里。

10. 联系实际:一次“改了域名还是访问旧机器”的分析

假设你把 app.example.com 从旧 IP 切到新 IP,但部分用户仍访问旧机器。

合理路径是:

  1. 查权威 DNS,确认新记录是否已经写入。
  2. 查递归解析器,确认它返回新 IP 还是旧 IP。
  3. 查看旧记录 TTL,判断缓存是否还在有效期。
  4. 检查客户端或应用是否有自己的 DNS 缓存。
  5. 检查 CNAME 链中是否还有旧目标。
  6. 确认访问日志里旧流量来自哪些网络和解析器。

如果旧 TTL 是 24 小时,那么一部分旧访问持续数小时甚至更久,并不奇怪。切换关键服务前,通常会提前降低 TTL,等旧缓存过期后再切换。

手推:TTL 时间线为什么会让“改了记录”不立刻生效

假设旧记录是:

app.example.com -> 旧 IP
TTL = 3600s

时间线:

t0 某递归解析器查询权威 DNS,拿到旧 IP,缓存 3600s
t0+600 你在权威 DNS 把记录改成新 IP
t0+700 用户通过这个递归解析器查询

用户仍可能拿到旧 IP,因为递归解析器的缓存还没过期:

剩余 TTL = 3600 - 700 = 2900s

这不是权威 DNS 没改,也不是用户机器坏了,而是缓存系统按旧承诺继续工作。更稳的切换应该是:

先把 TTL 降低
等待旧长 TTL 退场
再切换记录
观察旧地址流量下降
最后回调合适 TTL

DNS 变更的关键判断不是“我现在查权威是不是新值”,而是:

哪些递归解析器还合法地缓存着旧值?
客户端或应用有没有自己的缓存?
旧 IP 是否需要继续服务到缓存完全退场?

反例:解析成功,仍可能因为地址族选择失败

一个域名可能同时有:

A -> IPv4 地址
AAAA -> IPv6 地址

如果某个环境优先尝试 IPv6,但 IPv6 路由、出口、防火墙或目标服务有问题,就会出现:

DNS 解析成功
程序优先拿到或优先尝试 AAAA
连接阶段失败或等待超时
手动只测 IPv4 时看起来正常

这类问题容易被误叫成“DNS 解析失败”,但 DNS 已经完成了名字到地址的转换。真正的失败发生在解析之后的地址选择和连接路径。

排查时要分开记录:

证据说明
返回了哪些记录A、AAAA、CNAME 是否符合预期
程序实际选择哪个地址多地址返回不等于每个地址都被尝试
每个地址连接是否成功IPv4 与 IPv6 路径要分开测
失败发生在哪一段DNS、connect、TLS、应用响应不能混在一起

所以 DNS 排障不要只问“域名能不能解析”,还要问“解析出的哪个地址被使用,以及这个地址后续是否可达”。

排障卡:把“域名问题”拆成四段证据

很多线上问题被笼统地称作 DNS 问题,但真正的失败点可能在解析之后。排障时把证据拆成四段:

要确认什么典型证据
权威 DNS源头记录是否正确权威服务器返回新 A/AAAA/CNAME
递归解析器用户环境拿到什么答案不同解析器返回值、TTL 剩余时间
本机/应用缓存程序实际使用什么答案本机缓存、应用运行时缓存、容器内解析结果
连接与协议解析后的 IP 能否完成请求TCP 连接、TLS 握手、HTTP 状态、服务日志

这样拆开后,判断会更清楚:

权威是新 IP,递归还是旧 IP
-> 多半是 TTL 缓存尚未过期

递归和本机都是新 IP,程序仍访问旧 IP
-> 看应用自己的 DNS 缓存或运行环境差异

程序解析到新 IP,但连接失败
-> DNS 已经完成,继续查路由、防火墙、端口、TLS 或应用服务

小实验:模拟一次安全的域名切换

把真实服务切到新机器前,可以按这个流程设计演练:

  1. 变更前一天或数小时前,把关键记录 TTL 从长值降低到短值。
  2. 等旧 TTL 过期后,再修改 A/AAAA 或 CNAME 目标。
  3. 同时查询权威 DNS 和多个递归解析器,记录返回值与 TTL。
  4. 保留旧机器一段时间,观察访问日志中旧 IP 流量何时下降。
  5. 如果发现旧流量持续存在,检查应用缓存、固定 IP 配置、CNAME 链和客户端环境。

这个实验能建立一个重要直觉:DNS 切换不是“按下按钮全网立刻改变”,而是缓存逐级退场的过程。

11. 学完本章你能解决什么问题

学完本章,你应该能:

  1. 画出从客户端到递归解析器、根、顶级域、权威 DNS 的解析路径。
  2. 解释 A、AAAA、CNAME、NS、MX、TXT 等记录的基本作用。
  3. 判断 DNS TTL 如何影响变更生效、故障切换和查询压力。
  4. 区分解析失败、解析慢、解析到旧 IP、解析成功但连接失败。
  5. 解释为什么不同网络环境可能解析到不同 IP。
  6. 排查“命令行能解析但程序不能”“域名已改但仍访问旧机器”等问题。
  7. 理解 DNS 在流量调度和缓存节点选择中的作用与局限。

延伸阅读