当你在浏览器输入 www.example.com
并按下回车时,DNS
解析在毫秒级完成——但这个”毫秒”背后可能经历了六七层缓存和多次跨国网络查询。
大多数工程师知道 DNS 是”域名转 IP”,但不清楚这个转换过程中到底有多少参与者、每一层的缓存行为是什么、TTL 在哪里递减、否定缓存(Negative Caching)会导致什么问题。结果就是——DNS 相关的故障排查只能靠猜。
本文完整追踪一个 DNS 查询从浏览器到最终 IP 的每一跳,让你理解每一层在做什么、缓存了什么、TTL 怎么影响行为。
一、DNS 解析的完整链路
一个未缓存的 DNS 查询经历以下完整链路:
完整 DNS 解析链路:
① 浏览器 DNS 缓存
↓ 未命中
② 操作系统 DNS 缓存 (Stub Resolver)
↓ 未命中
③ 本地 DNS 缓存服务 (可选, 如 systemd-resolved / dnsmasq)
↓ 未命中
④ 递归解析器 (Recursive Resolver)
├→ 查根服务器: "com. 的 NS 是谁?"
├→ 查 TLD 服务器: "example.com. 的 NS 是谁?"
└→ 查权威服务器: "www.example.com. 的 A 是什么?"
⑤ 权威 DNS 服务器 (Authoritative Server)
→ 返回最终结果
结果沿链路返回,每一层缓存结果:
权威 → 递归 → 本地缓存 → OS → 浏览器 → 应用
时间开销:
全部缓存命中: < 1ms
OS 缓存命中: 1-5ms
递归解析器缓存命中: 5-30ms
完全未缓存(冷查询): 50-200ms(取决于 RTT)
1.1 各层角色
┌─────────────────────────────────────────────────────┐
│ 层级 │ 角色 │ 缓存行为 │
├─────────────────────────────────────────────────────┤
│ 浏览器 │ 应用层缓存 │ 独立 TTL │
│ OS Stub Resolver │ 系统级转发 │ 有限缓存 │
│ 本地 DNS 服务 │ 本地缓存 │ 遵守 TTL │
│ 递归解析器 │ 完整递归查询 │ 遵守 TTL + 预取 │
│ 根服务器 │ 指向 TLD │ 不缓存(权威) │
│ TLD 服务器 │ 指向权威 │ 不缓存(权威) │
│ 权威服务器 │ 返回最终结果 │ 不缓存(权威) │
└─────────────────────────────────────────────────────┘
注意:
- 根服务器和 TLD 服务器是权威服务器的一种,不做递归
- 递归解析器是唯一做完整递归查询的角色
- 权威服务器只回答自己负责的域名,不做递归
二、递归查询与迭代查询
DNS 查询有两种模式,理解它们的区别是理解 DNS 架构的关键:
2.1 递归查询(Recursive Query)
递归查询: 客户端要求 DNS 服务器返回最终结果
Client ──→ "请帮我查 www.example.com 的 A 记录"
Recursive Resolver: "好的,我来帮你查到最终结果"
├→ 问根: com 的 NS? → a.gtld-servers.net
├→ 问 TLD: example.com 的 NS? → a.iana-servers.net
└→ 问权威: www.example.com 的 A? → 93.184.216.34
Client ←── "结果是 93.184.216.34"
特点:
- 客户端只发一次查询,得到最终结果
- 递归解析器承担了所有中间查询的工作
- DNS Header 中 RD=1 (Recursion Desired)
- 递归解析器响应中 RA=1 (Recursion Available)
使用场景: 客户端(浏览器/应用)→ 递归解析器
2.2 迭代查询(Iterative Query)
迭代查询: DNS 服务器只返回"下一步去找谁"
Recursive Resolver ──→ 根服务器: "www.example.com 的 A?"
根服务器 ←── "我不知道,去问 a.gtld-servers.net (com 的 NS)"
Recursive Resolver ──→ TLD 服务器: "www.example.com 的 A?"
TLD 服务器 ←── "我不知道,去问 a.iana-servers.net (example.com 的 NS)"
Recursive Resolver ──→ 权威服务器: "www.example.com 的 A?"
权威服务器 ←── "93.184.216.34"
特点:
- 服务器不帮你递归,只告诉你"下一步找谁"
- 递归解析器负责逐步追踪
- 每一步都是独立的查询-响应
使用场景: 递归解析器 → 权威服务器(各级)
# 模拟迭代查询过程
# 1. 问根服务器
dig @a.root-servers.net www.example.com A +norecurse
# 响应: Authority Section 包含 com. 的 NS 记录
# 不会返回最终答案
# 2. 问 com TLD
dig @a.gtld-servers.net www.example.com A +norecurse
# 响应: Authority Section 包含 example.com. 的 NS 记录
# 3. 问 example.com 权威服务器
dig @a.iana-servers.net www.example.com A +norecurse
# 响应: Answer Section 包含 A 记录
# AA 标志被设置(Authoritative Answer)
# 用 +trace 一次看到完整过程
dig www.example.com A +trace2.3 递归 vs 迭代的关系
整体架构:
[Client] ──递归──→ [Recursive Resolver] ──迭代──→ [Root NS]
──迭代──→ [TLD NS]
──迭代──→ [Auth NS]
Client 到 Recursive Resolver: 递归查询
"帮我查到底,告诉我最终结果"
Recursive Resolver 到各级权威: 迭代查询
"一步步问,每次拿到下一步的线索"
为什么这样设计:
1. 客户端简单化 — 只需要和一个递归解析器通信
2. 递归解析器集中缓存 — 服务同一网络中的大量客户端
3. 权威服务器不做递归 — 减少被滥用的风险(放大攻击)
4. 分层设计 — 根/TLD/权威各自独立管理
三、Stub Resolver:操作系统的 DNS 客户端
Stub Resolver 是操作系统中最底层的 DNS 组件——它不做递归查询,只是将查询转发给配置好的递归解析器。
3.1 Linux 的 DNS 配置
# /etc/resolv.conf — DNS 配置文件
cat /etc/resolv.conf
# nameserver 8.8.8.8
# nameserver 8.8.4.4
# search example.com internal.example.com
# options ndots:5 timeout:2 attempts:3
# 字段说明:
# nameserver: 递归解析器 IP(最多 3 个)
# 按顺序尝试,第一个超时才试第二个
# 不是负载均衡(不是轮询)
# search: 搜索域列表
# 输入 "api" 时会尝试:
# api.example.com → api.internal.example.com → api
# 注意: 这会增加 DNS 查询次数
# options:
# ndots:5 — 域名中的 . 数量 < 5 时,先尝试搜索域
# "api.v2.svc" 有 2 个点 < 5 → 先追加搜索域
# "api.v2.svc.cluster.local" 有 4 个点 < 5 → 先追加搜索域
# 这是 Kubernetes DNS 问题的常见根源!
# timeout:2 — 查询超时 2 秒
# attempts:3 — 每个 nameserver 重试 3 次3.2 ndots 的工程影响
Kubernetes 中的 ndots 问题:
Pod 的 /etc/resolv.conf:
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
查询 "api.example.com" (2 个点 < 5):
1. api.example.com.default.svc.cluster.local → NXDOMAIN
2. api.example.com.svc.cluster.local → NXDOMAIN
3. api.example.com.cluster.local → NXDOMAIN
4. api.example.com → 成功!
一次查询变成了 4 次! (或者更多,如果查 A 和 AAAA)
优化方案:
方案 A: 使用 FQDN(以 . 结尾)
dig api.example.com. ← 注意末尾的点
告诉 resolver: 这是完整域名,不要追加搜索域
方案 B: 减小 ndots
dnsConfig:
options:
- name: ndots
value: "2"
代价: svc-name.namespace 这样的短名称需要写全
方案 C: 使用 Node Local DNS Cache
在节点上部署 DNS 缓存(如 CoreDNS / NodeLocal DNSCache)
减少对中心 DNS 的查询压力
# 观察 ndots 的影响
# 设置 ndots=5 时查询外部域名的实际 DNS 流量
tcpdump -i eth0 port 53 -c 20 &
dig api.example.com +short
# 抓包会显示多次查询:
# api.example.com.default.svc.cluster.local A?
# api.example.com.svc.cluster.local A?
# api.example.com.cluster.local A?
# api.example.com A?
# 使用 FQDN 避免多余查询
dig api.example.com. +short
# 只有一次查询: api.example.com A?3.3 systemd-resolved
# systemd-resolved 是现代 Linux 发行版的默认 DNS 解析服务
# 它在 Stub Resolver 和递归解析器之间增加了一层本地缓存
# 查看 systemd-resolved 状态
resolvectl status
# Global
# Current DNS Server: 8.8.8.8
# DNS Servers: 8.8.8.8 8.8.4.4
# DNS Domain: ~.
# Link 2 (eth0)
# Current DNS Server: 192.168.1.1
# DNS Servers: 192.168.1.1
# 查看缓存统计
resolvectl statistics
# DNSSEC supported by current servers: yes
# Transactions
# Current: 2
# Total: 15234
# Cache
# Current Size: 456
# Cache Hits: 12789
# Cache Misses: 2445
# 清除缓存
resolvectl flush-caches
# 查看特定域名的解析结果
resolvectl query example.com
# example.com: 93.184.216.34
# -- link: eth0
# -- Information acquired via protocol DNS in 3.2ms.
# -- Data is authenticated: no; Data was acquired via local or encrypted transport: no
# -- Data from: cache
# systemd-resolved 的监听地址
# 127.0.0.53:53 — Stub Resolver(/etc/resolv.conf 指向这里)
# 127.0.0.54:53 — 直接转发(不追加搜索域)
ss -ulnp | grep 'systemd-resolve'
# udp 127.0.0.53:53 * systemd-resolve
# udp 127.0.0.54:53 * systemd-resolve四、递归解析器(Recursive Resolver)
递归解析器是 DNS 架构中最关键的组件——它替客户端完成从根到权威的完整查询链路,并缓存结果以提高后续查询的速度。
4.1 递归解析器的工作流程
递归解析器处理查询 "www.example.com A" 的完整流程:
1. 接收查询
检查本地缓存 → 命中则直接返回
2. 缓存未命中 → 开始迭代查询
a. 检查根服务器 NS 的缓存
通常有缓存(根 NS 的 TTL = 518400 = 6 天)
b. 查根服务器: "com 的 NS?"
→ 根返回 com 的 NS 列表(委派应答)
→ 缓存 com 的 NS 记录
c. 查 com TLD: "example.com 的 NS?"
→ TLD 返回 example.com 的 NS 列表
→ 缓存 example.com 的 NS 记录
d. 查 example.com 权威: "www.example.com 的 A?"
→ 权威返回 A 记录
→ 缓存 www.example.com 的 A 记录
3. 返回结果给客户端
4. 后续相同查询
直接从缓存返回(在 TTL 内)
4.2 公共 DNS 服务对比
┌──────────────────────┬────────────┬──────────┬───────────────┐
│ 服务 │ IP 地址 │ DNSSEC │ 特点 │
├──────────────────────┼────────────┼──────────┼───────────────┤
│ Google Public DNS │ 8.8.8.8 │ 支持 │ 全球节点最多 │
│ │ 8.8.4.4 │ │ ECS 支持 │
├──────────────────────┼────────────┼──────────┼───────────────┤
│ Cloudflare DNS │ 1.1.1.1 │ 支持 │ 延迟最低 │
│ │ 1.0.0.1 │ │ 隐私承诺 │
├──────────────────────┼────────────┼──────────┼───────────────┤
│ Quad9 │ 9.9.9.9 │ 支持 │ 安全过滤 │
│ │ 149.112.112│ │ 恶意域名拦截 │
├──────────────────────┼────────────┼──────────┼───────────────┤
│ 阿里 DNS │ 223.5.5.5 │ 支持 │ 国内节点多 │
│ │ 223.6.6.6 │ │ ECS 支持 │
├──────────────────────┼────────────┼──────────┼───────────────┤
│ 腾讯 DNS (DNSPod) │ 119.29.29.29│ 支持 │ 国内延迟低 │
├──────────────────────┼────────────┼──────────┼───────────────┤
│ 运营商 DNS │ DHCP 分配 │ 不确定 │ CDN 调度准确 │
└──────────────────────┴────────────┴──────────┴───────────────┘
# 测量不同公共 DNS 的查询延迟
for dns in 8.8.8.8 1.1.1.1 9.9.9.9 223.5.5.5 119.29.29.29; do
echo -n "$dns: "
dig @$dns example.com A +noall +stats | grep "Query time"
done
# 典型国内结果:
# 8.8.8.8: Query time: 35 msec
# 1.1.1.1: Query time: 25 msec
# 9.9.9.9: Query time: 40 msec
# 223.5.5.5: Query time: 8 msec
# 119.29.29.29: Query time: 5 msec
# 注意: 第一次查询可能较慢(缓存预热),多次查询取平均值4.3 运营商 DNS vs 公共 DNS
运营商 DNS 的优缺点:
优点:
✓ CDN 调度最准确
运营商 DNS 的出口 IP 与你在同一网络
CDN 根据 DNS 解析器的 IP 判断用户位置
使用运营商 DNS → CDN 返回最近的节点
使用 8.8.8.8 → CDN 可能返回香港或美国节点
✓ 延迟通常最低
运营商 DNS 在本地机房,RTT < 5ms
缺点:
✗ 可能存在 DNS 劫持
将不存在的域名重定向到广告页面
将特定域名重定向到其他服务器
✗ DNSSEC 支持不确定
很多运营商 DNS 不做 DNSSEC 验证
✗ 稳定性可能较差
运营商 DNS 偶尔出现故障
公共 DNS 的优缺点:
优点: 稳定、安全(DNSSEC)、无劫持
缺点: CDN 调度可能不准确(除非支持 ECS)
ECS (EDNS Client Subnet) 的作用:
公共 DNS 将客户端子网信息发给权威服务器
权威服务器可以根据客户端真实位置返回最近的 CDN 节点
Google DNS 和阿里 DNS 支持 ECS
→ 缓解了公共 DNS 的 CDN 调度问题
五、权威 DNS 服务器
权威服务器是 DNS 数据的源头——它存储域名的实际记录,不做递归查询,只回答自己负责的域名。
5.1 权威服务器架构
权威 DNS 架构:
┌─────────────────┐ AXFR/IXFR ┌─────────────────┐
│ Primary (Master) │ ──────────────────→ │ Secondary (Slave)│
│ 区域文件读写 │ │ 区域文件只读 │
│ 接受动态更新 │ │ 同步主服务器数据 │
└─────────────────┘ └─────────────────┘
↑ ↑
│ │
管理员修改 递归解析器查询
区域文件 (负载分担)
同步机制:
1. AXFR (全量传输): 传输整个区域文件
用于: 初次同步、数据损坏恢复
协议: TCP(数据量大)
2. IXFR (增量传输): 只传输变更部分
用于: 日常同步,基于 SOA SERIAL 差异
协议: TCP 或 UDP
3. NOTIFY (通知): 主服务器变更后主动通知从服务器
不等 REFRESH 间隔,立即触发同步
5.2 常见权威 DNS 软件
# BIND (Berkeley Internet Name Domain)
# 最古老、最广泛使用的 DNS 软件
# 区域文件示例 (/etc/bind/zones/db.example.com):
# $ORIGIN example.com.
# $TTL 3600
# @ IN SOA ns1.example.com. admin.example.com. (
# 2024010101 ; Serial
# 7200 ; Refresh
# 3600 ; Retry
# 1209600 ; Expire
# 3600 ; Negative TTL
# )
# @ IN NS ns1.example.com.
# @ IN NS ns2.example.com.
# @ IN A 93.184.216.34
# @ IN AAAA 2606:2800:220:1:248:1893:25c8:1946
# @ IN MX 10 mail.example.com.
# www IN A 93.184.216.34
# mail IN A 93.184.216.35
# ns1 IN A 199.43.135.53
# ns2 IN A 199.43.133.53
# PowerDNS — 支持多种后端(MySQL, PostgreSQL, LDAP)
# 适合需要 API 管理 DNS 记录的场景
# Knot DNS — 高性能权威 DNS
# 捷克 CZ.NIC 开发,多个 TLD 使用
# NSD (Name Server Daemon) — 轻量级权威 DNS
# NLnet Labs 开发,多个根服务器使用六、DNS 缓存层级与 TTL 行为
缓存是 DNS 性能的核心——没有缓存,每次域名查询都要从根服务器开始迭代,延迟将从毫秒级膨胀到数百毫秒。但缓存也引入了一致性问题。
6.1 各级缓存的行为
浏览器缓存:
存储: 最近查询的域名 → IP 映射
TTL: 通常固定(Chrome 默认 60 秒,不完全遵守 DNS TTL)
清除: 关闭标签页或手动清除
Chrome 查看/清除 DNS 缓存:
chrome://net-internals/#dns
→ Clear host cache
OS 缓存 (systemd-resolved / nscd):
存储: 所有应用的 DNS 查询结果
TTL: 遵守 DNS TTL(或有最大/最小 TTL 限制)
清除:
systemd-resolved: resolvectl flush-caches
nscd: nscd -i hosts
macOS: sudo dscacheutil -flushcache
递归解析器缓存:
存储: 所有查询结果(最大的 DNS 缓存层)
TTL: 严格遵守权威服务器设置的 TTL
特性:
- 服务大量客户端,缓存命中率最高
- 支持预取(TTL 即将到期时提前刷新)
- 缓存 NS 记录减少后续查询的迭代次数
清除: 通常无法由客户端清除(除非是自己管理的解析器)
6.2 TTL 递减行为
TTL 在 DNS 系统中的行为:
权威服务器返回: TTL=3600 (1 小时)
递归解析器缓存时:
存储 TTL=3600
每次有客户端查询时,返回剩余 TTL
30 分钟后查询: 返回 TTL=1800
59 分钟后查询: 返回 TTL=60
60 分钟后: 缓存过期,需要重新查询权威服务器
客户端(OS / 浏览器)缓存时:
从递归解析器收到 TTL=1800(已经过了一半)
客户端缓存 1800 秒后过期
如果递归解析器的缓存先过期,下次客户端查询会得到新的 TTL
# 观察 TTL 递减
# 第一次查询
dig @8.8.8.8 example.com A | grep "^example"
# example.com. 3543 IN A 93.184.216.34
# 等待 10 秒
sleep 10
# 第二次查询
dig @8.8.8.8 example.com A | grep "^example"
# example.com. 3533 IN A 93.184.216.34
# TTL 减少了 10
# 冷查询(清除缓存后的第一次查询)会看到完整的 TTL
# 用 +trace 绕过递归解析器缓存
dig example.com A +trace | grep "^www\|^example" | head -36.3 否定缓存(Negative Caching)
否定缓存是 DNS 中一个经常被忽视但影响巨大的特性:
否定缓存的工作原理:
查询一个不存在的域名:
dig nonexistent.example.com A
# status: NXDOMAIN
递归解析器缓存这个 NXDOMAIN 响应
缓存时间 = SOA 记录的 MINIMUM 字段值
dig example.com SOA +short
# ns.icann.org. noc.dns.icann.org. 2024010101 7200 3600 1209600 3600
# ^^^^
# MINIMUM = 3600 秒 (1 小时)
在这 1 小时内:
- 再次查询 nonexistent.example.com → 直接返回缓存的 NXDOMAIN
- 即使你在这 1 小时内添加了这个 DNS 记录
- 新记录不会立即生效,需要等否定缓存过期
工程影响:
1. 新域名上线后部分用户无法访问
用户在域名配置前访问过 → 被缓存了 NXDOMAIN
需要等 SOA MINIMUM 时间后才能正常解析
2. 测试时频繁遇到"域名不存在"
DNS 记录刚添加,但否定缓存还未过期
3. 缓解措施:
- 设置较短的 SOA MINIMUM(如 300 秒)
- 但太短会增加不存在域名的查询压力
- 推荐值: 300-3600 秒
# 观察否定缓存
dig @8.8.8.8 nonexistent-test-12345.example.com A +noall +comments
# status: NXDOMAIN
# authority section 会包含 SOA 记录(否定缓存的 TTL 来源)
dig @8.8.8.8 nonexistent-test-12345.example.com A +noall +authority
# example.com. 3600 IN SOA ns.icann.org. ...
# 这个 3600 就是否定缓存的 TTL
# 第二次查询(从缓存返回)
dig @8.8.8.8 nonexistent-test-12345.example.com A +noall +comments
# status: NXDOMAIN
# 但这次是从缓存返回的(更快)6.4 缓存投毒与防御
DNS 缓存投毒攻击原理:
正常流程:
Client → Recursive → 权威: "example.com A?"
权威 → Recursive: "93.184.216.34"(正确答案)
攻击者目标: 在权威真正回答前,伪造一个响应
Recursive → 权威: "example.com A?" (Transaction ID=0x1234)
攻击者: 疯狂发送伪造响应:
Source: 伪造为权威服务器 IP
Transaction ID: 猜测(0x0001, 0x0002, ..., 0xFFFF)
Answer: "example.com A → 恶意 IP"
如果猜对 Transaction ID → 递归解析器缓存了恶意 IP
后续所有查询这个域名的用户都被重定向
防御措施:
1. 随机化 Transaction ID (16-bit, 65536 种)
2. 随机化源端口 (增加熵, Kaminsky 攻击的缓解)
3. DNSSEC (密码学验证响应的真实性)
4. DNS Cookie (RFC 7873, 轻量级验证)
5. 0x20 编码 (随机化查询域名的大小写作为额外熵)
# 检查递归解析器是否使用随机源端口
dig @your-dns-server example.com A
# 用 tcpdump 观察源端口
tcpdump -i eth0 dst port 53 -n | head -5
# 如果每次查询的源端口不同 → 使用了随机源端口
# 如果源端口固定 → 容易被缓存投毒
# 检查 DNSSEC 验证是否启用
dig @8.8.8.8 example.com A +dnssec +noall +comments
# flags: ... ad ← AD (Authentic Data) 标志表示 DNSSEC 验证通过七、DNS 解析性能分析
7.1 解析延迟的组成
DNS 解析延迟分解:
完全未缓存(冷查询):
根查询 RTT: 10-30ms (Anycast,通常较快)
TLD 查询 RTT: 15-40ms
权威查询 RTT: 20-100ms (取决于权威服务器位置)
总延迟: 45-170ms
递归解析器有部分缓存:
根 NS 缓存命中: 省 10-30ms
TLD NS 缓存命中: 省 15-40ms
只需查权威: 20-100ms
递归解析器完全缓存:
客户端 → 递归: 1-30ms (取决于网络距离)
本地 DNS 缓存命中:
查询延迟: < 1ms
# 测量 DNS 解析对 HTTP 请求延迟的影响
# 含 DNS 解析的 HTTP 请求
curl -w "\
DNS Lookup: %{time_namelookup}s\n\
TCP Connect: %{time_connect}s\n\
TLS Handshake:%{time_appconnect}s\n\
TTFB: %{time_starttransfer}s\n\
Total: %{time_total}s\n" \
-o /dev/null -s https://example.com
# 典型结果:
# DNS Lookup: 0.025s ← DNS 解析 25ms
# TCP Connect: 0.055s ← TCP 握手额外 30ms
# TLS Handshake: 0.115s ← TLS 握手额外 60ms
# TTFB: 0.165s ← 服务端处理 50ms
# Total: 0.170s
# DNS 占总延迟的比例: 25/170 = 15%
# 如果 DNS 未缓存(冷查询): 可能 100-200ms
# DNS 占比可能高达 50%+7.2 预取(Prefetching)
DNS 预取策略:
浏览器预取:
HTML 中可以声明预取:
<link rel="dns-prefetch" href="//api.example.com">
<link rel="dns-prefetch" href="//cdn.example.com">
浏览器在空闲时预先解析这些域名
递归解析器预取:
某些递归解析器支持在 TTL 即将到期时提前刷新缓存
例如 Unbound 的 prefetch 配置:
prefetch: yes
prefetch-key: yes
在 TTL 剩余 10% 时自动发起后台查询
→ 客户端永远不会遇到缓存过期的冷查询
应用层预取:
在应用启动时预热 DNS 缓存:
# 微服务启动时预解析所有依赖服务的域名
for dep in db.internal api.internal cache.internal; do
getent hosts $dep > /dev/null
done
7.3 DNS over TCP 的性能影响
DNS over TCP vs UDP 的性能对比:
UDP (标准):
连接开销: 0 (无连接)
查询延迟: 1 RTT
适用: 绝大多数 DNS 查询
TCP:
连接开销: 1 RTT (TCP 握手)
查询延迟: 2 RTT (握手 + 查询)
适用: 大响应(>UDP bufsize)、区域传输、某些安全策略
TCP 连接复用 (RFC 7766):
第一次查询: 2 RTT
后续查询: 1 RTT (复用已有连接)
但: 大多数实现会在空闲后关闭连接
TCP Fast Open (TFO) for DNS:
首次查询: 2 RTT
后续查询: 1 RTT (TFO 在 SYN 中携带数据)
支持: 较新的 DNS 实现开始支持
八、DNS 解析器选型
8.1 自建 vs 托管
┌────────────────────┬──────────────────┬──────────────────┐
│ 方案 │ 优点 │ 缺点 │
├────────────────────┼──────────────────┼──────────────────┤
│ 公共 DNS │ 零维护 │ 无法自定义 │
│ (8.8.8.8/1.1.1.1) │ 全球 Anycast │ 隐私顾虑 │
│ │ 高可用 │ CDN 调度可能偏差 │
├────────────────────┼──────────────────┼──────────────────┤
│ 运营商 DNS │ 零维护 │ 可能劫持 │
│ │ CDN 调度准确 │ DNSSEC 不确定 │
│ │ 延迟最低 │ 稳定性不确定 │
├────────────────────┼──────────────────┼──────────────────┤
│ 自建递归 │ 完全控制 │ 需要运维 │
│ (Unbound/BIND) │ 可自定义策略 │ 需要高可用 │
│ │ 审计日志 │ 需要监控 │
├────────────────────┼──────────────────┼──────────────────┤
│ 转发器 + 缓存 │ 本地缓存加速 │ 依赖上游 DNS │
│ (dnsmasq/CoreDNS) │ 可加内部域名 │ 缓存一致性 │
│ │ 配置简单 │ │
└────────────────────┴──────────────────┴──────────────────┘
8.2 常见 DNS 软件
# === Unbound ===
# 轻量级递归解析器,安全性好,适合做本地/企业递归 DNS
# 安装
apt install unbound
# 关键配置 (/etc/unbound/unbound.conf):
# server:
# interface: 0.0.0.0
# access-control: 10.0.0.0/8 allow
# num-threads: 4
# prefetch: yes ← 预取,减少冷查询
# cache-min-ttl: 60 ← 最小缓存时间
# cache-max-ttl: 86400 ← 最大缓存时间
# val-clean-additional: yes ← DNSSEC 清理
# aggressive-nsec: yes ← 积极 NSEC,减少 NXDOMAIN 查询
# 检查配置
unbound-checkconf
# 查看统计
unbound-control stats_noreset | grep total
# === CoreDNS ===
# 云原生 DNS,Kubernetes 的默认 DNS
# 插件化架构,配置为 Corefile
# Corefile 示例:
# .:53 {
# forward . 8.8.8.8 1.1.1.1
# cache 3600
# log
# errors
# }
#
# cluster.local:53 {
# kubernetes cluster.local in-addr.arpa ip6.arpa
# cache 30
# }
# === dnsmasq ===
# 轻量级 DNS/DHCP 服务器,适合小型网络和开发环境
# 简单配置 (/etc/dnsmasq.conf):
# server=8.8.8.8
# server=1.1.1.1
# cache-size=10000
# no-resolv ← 不读 /etc/resolv.conf
# listen-address=127.0.0.1
# address=/internal.dev/127.0.0.1 ← 自定义域名解析九、案例:DNS 解析链路的故障排查
9.1 案例:网站上线后部分用户无法访问
现象:
新网站 www.newsite.com 上线
DNS 记录已添加: www.newsite.com A 203.0.113.1
部分用户可以访问,部分用户报告"域名无法解析"
排查过程:
1. 确认记录已在权威服务器生效
dig @ns1.newsite.com www.newsite.com A +short
# 203.0.113.1 ← 权威服务器有记录
2. 检查不同递归解析器的响应
dig @8.8.8.8 www.newsite.com A +short
# 203.0.113.1 ← Google DNS 已更新
dig @1.1.1.1 www.newsite.com A +short
# (空) ← Cloudflare DNS 还没有?
dig @1.1.1.1 www.newsite.com A +noall +comments
# status: NXDOMAIN ← 否定缓存!
3. 根因: 否定缓存
有用户在 DNS 记录添加前访问过 www.newsite.com
递归解析器缓存了 NXDOMAIN 响应
缓存时间 = SOA MINIMUM (可能是 3600 秒)
需要等缓存过期后才能正常解析
4. 解决:
短期: 通知用户清除本地 DNS 缓存
或等待否定缓存过期
长期: 设置较短的 SOA MINIMUM (如 300 秒)
上线前先添加 DNS 记录并等待传播
# 排查脚本: 检查域名在多个 DNS 上的解析状态
#!/bin/bash
DOMAIN=${1:-www.example.com}
DNS_SERVERS="8.8.8.8 1.1.1.1 9.9.9.9 223.5.5.5 119.29.29.29"
echo "=== DNS Resolution Check for $DOMAIN ==="
for dns in $DNS_SERVERS; do
result=$(dig @$dns $DOMAIN A +short +time=3 2>/dev/null)
status=$(dig @$dns $DOMAIN A +noall +comments +time=3 2>/dev/null \
| grep "status:" | awk -F'status: ' '{print $2}' \
| awk -F',' '{print $1}')
ttl=$(dig @$dns $DOMAIN A +noall +answer +time=3 2>/dev/null \
| awk '{print $2}' | head -1)
printf "%-16s status=%-10s TTL=%-6s result=%s\n" \
"$dns" "$status" "${ttl:-N/A}" "${result:-EMPTY}"
done9.2 案例:Kubernetes DNS 查询放大
现象:
CoreDNS Pod CPU 使用率异常高
应用日志显示 DNS 查询延迟偶尔达到数百毫秒
排查:
1. 检查 CoreDNS 日志
kubectl logs -n kube-system coredns-xxx | tail -20
# 大量查询 external-api.example.com
2. 分析查询模式
# 进入应用 Pod
kubectl exec -it app-pod -- cat /etc/resolv.conf
# nameserver 10.96.0.10
# search default.svc.cluster.local svc.cluster.local cluster.local
# options ndots:5
3. 抓包观察实际 DNS 查询
kubectl exec -it app-pod -- tcpdump -i eth0 port 53 -c 20
# external-api.example.com.default.svc.cluster.local A?
# external-api.example.com.svc.cluster.local A?
# external-api.example.com.cluster.local A?
# external-api.example.com A?
# 加上 AAAA 查询,总共 8 次 DNS 查询!
4. 根因: ndots=5 导致外部域名查询放大
"external-api.example.com" 有 2 个点 < 5
先尝试 3 个搜索域(每个都返回 NXDOMAIN)
然后才查原始域名
A + AAAA 查询 → 8 次 DNS 查询
5. 修复:
方案 A: 代码中使用 FQDN
url = "https://external-api.example.com." // 末尾加点
方案 B: 调整 Pod DNS 配置
spec:
dnsConfig:
options:
- name: ndots
value: "2"
方案 C: 部署 NodeLocal DNSCache
在每个节点上缓存 DNS 查询
减少 CoreDNS 的压力
十、结论
DNS 解析的”简单外表”背后是一个精密的分层缓存系统。理解每一层的角色和缓存行为,是排查 DNS 故障、优化解析性能的基础。
几个核心要点:
递归和迭代是两回事。 客户端到递归解析器是递归查询(“帮我查到底”),递归解析器到各级权威是迭代查询(“告诉我下一步找谁”)。很多 DNS 文档混淆了这两个概念。
缓存是 DNS 性能的核心。 没有缓存,每次查询都要从根服务器开始迭代,延迟从毫秒级变成百毫秒级。理解 TTL 递减行为和各级缓存的作用,是优化 DNS 性能的关键。
否定缓存是最容易被忽视的故障源。 在 DNS 记录配置生效前访问域名,否定缓存会导致”记录明明配了但就是查不到”。解决方案是设置合理的 SOA MINIMUM 值,并在上线前提前配置 DNS。
ndots 是 Kubernetes DNS 性能的隐形杀手。 默认 ndots=5 导致外部域名查询被放大 4-8 倍。在 K8s 环境中,优化 ndots 或使用 FQDN 是必须的。
公共 DNS vs 运营商 DNS 各有优劣。 公共 DNS 稳定安全但 CDN 调度可能不准(除非支持 ECS),运营商 DNS CDN 调度好但可能存在劫持。根据场景选择,或使用支持 ECS 的公共 DNS。
下一篇我们聚焦 DNS 性能优化——预取策略、TTL 调优、本地缓存部署,系统性地减少 DNS 解析对应用性能的影响。
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【网络工程】DNS 性能优化:预取、TTL 策略与本地缓存
DNS 解析延迟直接影响用户体验和服务可用性。本文从浏览器 DNS Prefetch、服务端预解析、TTL 策略设计、本地 DNS 缓存部署(systemd-resolved / dnsmasq / CoreDNS)四个维度,系统性地分析 DNS 性能优化的工程实践,包含延迟量化、缓存命中率提升和故障切换加速的完整方案。
网络工程索引
汇总本站网络工程系列文章,覆盖分层模型、以太网、IP、TCP、DNS、TLS、HTTP/2/3、CDN、BGP 与故障诊断。
【网络工程】CDN 架构原理:PoP、边缘节点与 Origin Shield
系统解剖 CDN 的多层缓存架构——从 DNS 调度到 PoP 内部结构、Origin Shield 回源保护、多 CDN 部署策略。结合实际配置和响应头分析,给出 CDN 架构的工程理解。
【网络工程】CDN 缓存策略:TTL、Purge 与 stale-while-revalidate
深入剖析 CDN 缓存策略的工程实践——TTL 设置方法论、Purge 机制与一致性保证、stale-while-revalidate 的工程价值、缓存命中率优化与常见缓存问题排查。