dns
08. DNS
0. 本章先解决什么问题
程序最终要用 IP 地址通信,但人和系统配置通常使用域名。
www.example.com -> 目标 IP
这一步看似简单,实际会影响请求的第一段延迟、流量调度、故障切换、缓存一致性和排障路径。
本章要解决:
- 域名是怎样被逐级解析成 IP 的。
- DNS 缓存和 TTL 为什么会让“改了配置但还没生效”成为正常现象。
- DNS 为什么既是命名系统,也是流量调度系统的一部分。
- 排障时怎样区分“解析失败”和“解析成功但连接失败”。
这张图怎么读
DNS 的关键不是“域名变 IP”这么简单,而是分层查询和多级缓存。TTL 让旧答案在一段时间内继续存在,这就是域名变更不会瞬间全网生效的根本原因。
1. 域名是分层名字
域名从右到左层级越来越具体:
| 部分 | 含义 |
|---|---|
. | 根域 |
com | 顶级域 |
example | 二级域 |
www | 子域或主机名 |
最后的点表示根域,日常使用时通常省略。
分层设计的好处是权威可以逐级下放:
根域管理顶级域在哪里
顶级域管理 example.com 的权威服务器在哪里
example.com 的权威服务器管理 www.example.com 的记录
如果所有域名都由一个中心数据库保存,规模、性能和管理边界都会非常糟糕。DNS 的分层结构让全球命名系统可以分布式运行。
2. 解析过程:递归解析器帮你一路问
普通主机通常不会自己从根域一路问到底,而是把问题交给递归解析器。
简化流程:
更具体一点:
- 应用请求解析 www.example.com
- 系统先查本地缓存/hosts 等本地来源
- 如果没有,询问配置的递归解析器
- 递归解析器如果没有缓存,询问根域
- 根域告诉它 com 的服务器在哪里
- com 告诉它 example.com 的权威服务器在哪里
- 权威服务器返回 www.example.com 的记录
- 递归解析器缓存结果并返回给客户端
解析器像一个代办员:客户端问一次,它负责把完整链路问完。
3. 递归查询和迭代查询
两个概念要区分:
| 查询方式 | 谁继续问下一级 | 常见位置 |
|---|---|---|
| 递归查询 | 被问的一方负责给最终答案 | 客户端问递归解析器 |
| 迭代查询 | 被问的一方只告诉你下一步去问谁 | 递归解析器问根/顶级域 |
客户端通常说:
请你直接给我最终答案。
根域、顶级域通常说:
我不知道最终答案,但你可以去问这个权威服务器。
这个分工让客户端简单,也让 DNS 能分层扩展。
4. 常见 DNS 记录
| 类型 | 作用 | 例子含义 |
|---|---|---|
| A | 域名到 IPv4 | example.com -> 93.184.216.34 |
| AAAA | 域名到 IPv6 | example.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 服务器慢、网络丢包 |
| 解析到旧 IP | TTL 缓存未过期、应用缓存 |
| 解析到多个 IP 但部分失败 | 某些上游节点异常、路由不通 |
| 能解析但连不上 | TCP/UDP 连接问题,不是 DNS 本身 |
| 命令行能解析,程序不能 | 程序运行环境 DNS 配置不同或内部缓存不同 |
一个请求失败时,DNS 只负责第一段:
域名 -> IP
之后还有:
IP 路由
传输层连接
TLS 握手
应用协议请求响应
9. 排障路径
排查 DNS 问题时,可以按这个顺序:
- 域名拼写是否正确。
- 本机 hosts 或本地配置是否覆盖。
- 当前环境使用哪个 DNS 解析器。
- 解析结果是否存在 A/AAAA/CNAME 等预期记录。
- TTL 是否导致旧结果仍然有效。
- 不同网络环境解析结果是否一致。
- 解析出的 IP 是否能建立连接。
- 目标程序和你手动测试是否在同一个运行环境。
不要只在宿主机手动解析一次就结束。程序可能运行在容器、虚拟机、沙箱、不同网络命名空间或不同配置环境里。
10. 联系实际:一次“改了域名还是访问旧机器”的分析
假设你把 app.example.com 从旧 IP 切到新 IP,但部分用户仍访问旧机器。
合理路径是:
- 查权威 DNS,确认新记录是否已经写入。
- 查递归解析器,确认它返回新 IP 还是旧 IP。
- 查看旧记录 TTL,判断缓存是否还在有效期。
- 检查客户端或应用是否有自己的 DNS 缓存。
- 检查 CNAME 链中是否还有旧目标。
- 确认访问日志里旧流量来自哪些网络和解析器。
如果旧 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 或应用服务
小实验:模拟一次安全的域名切换
把真实服务切到新机器前,可以按这个流程设计演练:
- 变更前一天或数小时前,把关键记录 TTL 从长值降低到短值。
- 等旧 TTL 过期后,再修改 A/AAAA 或 CNAME 目标。
- 同时查询权威 DNS 和多个递归解析器,记录返回值与 TTL。
- 保留旧机器一段时间,观察访问日志中旧 IP 流量何时下降。
- 如果发现旧流量持续存在,检查应用缓存、固定 IP 配置、CNAME 链和客户端环境。
这个实验能建立一个重要直觉:DNS 切换不是“按下按钮全网立刻改变”,而是缓存逐级退场的过程。
11. 学完本章你能解决什么问题
学完本章,你应该能:
- 画出从客户端到递归解析器、根、顶级域、权威 DNS 的解析路径。
- 解释 A、AAAA、CNAME、NS、MX、TXT 等记录的基本作用。
- 判断 DNS TTL 如何影响变更生效、故障切换和查询压力。
- 区分解析失败、解析慢、解析到旧 IP、解析成功但连接失败。
- 解释为什么不同网络环境可能解析到不同 IP。
- 排查“命令行能解析但程序不能”“域名已改但仍访问旧机器”等问题。
- 理解 DNS 在流量调度和缓存节点选择中的作用与局限。