前几章讲协议"应该怎么工作",本章讲它"实际上哪里坏了"。DNS 是唯一能把整条链路拖慢数秒的旁路系统。

学习目标

读完本章,你应当能够:

  1. 画出 DNS 递归查询与迭代查询的两条路径,说清每一层的缓存位置、TTL 归属与负缓存作用范围;
  2. 判断一组 DNS 记录能否合法共存(CNAME 与 MX、CNAME 与根域名),解释"切换 IP 前为什么要调小 TTL"的量化依据;
  3. 用 dig +trace、+short、@server、+tcp、+noedns 定位解析异常,说明 nslookup 在排障中的信息缺失;
  4. 对一次"网站访问慢"给出分层命令序列,按 curl -w 的 time_* 差值定位到 DNS、建连、TLS、TTFB、传输中的哪一段;
  5. 写出 tcpdump 的主机/端口/标志位过滤表达式,并判断握手是否完成、有无重传、RST、零窗口;
  6. 辨析 ping/traceroute/mtr/telnet/nc/ss 各自的能力边界,在 ICMP 被禁时给出替代手段。

前置知识检查

你需要知道在哪一节补齐
UDP 语义、TCP 三次握手与状态名(SYN_SENT/ESTABLISHED/TIME_WAIT)见第 16 章 16.1、16.3 节;本章只用它们读抓包结果
重传、重复 ACK、零窗口的成因见第 17 章 17.2、17.4 节;本章只讲"抓包时怎么看出来"
TLS 握手往返数、SNI、证书链见第 20 章 20.5 节;本章只用 time_appconnect 度量它
IP 分片、MTU、UDP 报文长度上限见第 15 章 15.4 节;本章用来说明 DNS 的 TC 截断

19.1 DNS 的完整查询流程:递归与迭代是两条路径

面试里"讲一下 DNS 解析过程"最常见的错误回答,是把浏览器缓存、根服务器、权威服务器串成一条线性链。真实的 DNS 是两段拼接:客户端到本地 DNS 服务器(local DNS server,也常叫递归解析器 recursive resolver)是递归,本地 DNS 服务器到根/TLD/权威是迭代。两段的责任划分、缓存归属、故障表现都不同。

19.1.1 两段路径的全图

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
 客户端(浏览器/应用)                   本地 DNS 服务器          根/TLD/权威
 ┌────────────────────────┐            ┌──────────────┐      ┌──────────────┐
 │ ① 浏览器 DNS 缓存       │            │              │      │              │
 │ ② OS 解析器             │            │              │      │              │
 │    nsswitch.conf:      │            │              │      │              │
 │    hosts: files dns    │            │              │      │              │
 │    → /etc/hosts 命中即止│            │              │      │              │
 │ ③ OS DNS 缓存           │            │              │      │              │
 │ ④ 读 /etc/resolv.conf   │  递归查询   │              │      │              │
 │    nameserver 10.0.0.53─┼───────────▶│              │      │              │
 └────────────────────────┘   (RD=1)    │ ⑤ 查自身缓存  │      │              │
                                        │   未命中 ↓    │      │              │
                                        │ ⑥ 问根 ───────┼─────▶│ .  返回 TLD NS│
                                        │ ⑦ 问 TLD ─────┼─────▶│ com. 返回权威 │
                                        │ ⑧ 问权威 ─────┼─────▶│ 返回 A 记录   │
                                        │ ⑨ 按 TTL 缓存 │      │              │
                                        └───────┬───────┘      └──────────────┘
                                                │ 递归应答 (RA=1)
                                                ▼
                                         客户端拿到 A 记录

三个容易答错的点。第一,/etc/hosts 优先于 DNS 不是"永远优先",而由 nsswitch.conf 里 hosts: 一行的顺序决定:写成 hosts: dns files 就反过来,写成 hosts: files myhostname dns 是多数发行版默认。面试问"改了 hosts 不生效",第一个要看这一行,第二个是应用自己缓存了(JVM 的 networkaddress.cache.ttl、Go 的 GODEBUG=netdns)。第二,本地 DNS 服务器自己也会缓存,所以第 ⑥~⑧ 步通常根本不发生——这也是"改了权威记录但不生效"的第一大原因,不是你的 TTL 没到,是别人的缓存还没到期。第三,递归与迭代的分界在本地 DNS 服务器:客户端发的是 RD=1(recursion desired)请求,只关心最终答案;本地 DNS 发的是 RD=0 请求,每问一台就要自己接着问下一台。根服务器从不做递归,以避免被放大攻击利用。

重要

答题模板固定为:“客户端→本地 DNS 是递归,本地 DNS→根→TLD→权威是迭代”。只答"一层层问下去"会被追问"那谁负责帮你问完",这个追问就是在区分你是否知道 RD 标志位与两段责任划分。

19.1.2 TTL 与负缓存:为什么"删除记录"不一定立刻失效

缓存位置缓存什么有效期由谁决定排障时怎么绕过
浏览器 DNS 缓存域名 → IP浏览器自定上限(常为 60 s 量级)清浏览器缓存、开隐身窗口
OS 解析器缓存域名 → IP记录的 TTLresolvectl flush-caches
/etc/hosts静态映射永不过期不适用(它是覆盖源,不是缓存)
本地 DNS 服务器正/负应答正应答按 TTL;负应答按 SOA 的 minimum换 @8.8.8.8 这类公共解析器对比
权威服务器不缓存它是数据源不适用

负缓存(negative caching) 是面试的高区分度点:查询不存在的名字时,权威返回 NXDOMAIN 并附上 SOA 记录,本地 DNS 按 SOA 的 minimum 字段(RFC 2308 口径)把这个"不存在"缓存一段时间。后果是:刚上线的新域名若已被解析器查过一次,必须等负缓存过期才能解析成功,表现为"我这边能通、别人那边 NXDOMAIN"。因此上线顺序永远是先建记录再切流量。同理,TTL 设为 0 也不等于"不缓存":多数解析器有秒级下限(RFC 1035 里 0 只表示"不要复用"),客户端缓存更不受你的 TTL 完全支配。

19.1.3 切换 IP 时必须提前调小 TTL:一个算例

TTL 决定缓存能活多久,所以变更生效的最坏时间 ≈ 当前 TTL。切换 IP 的标准动作是两阶段:提前至少一个旧 TTL 的时长把 TTL 从 86400(1 天)改成 60,等旧的长 TTL 缓存全部过期;然后改记录值(A → 新 IP)并等 60 s 让新值扩散;稳定后再把 TTL 调回 86400,降低解析器压力与放大攻击面。

算例:TTL = 86400 时直接改 IP,最坏情况下一部分客户端 24 小时内仍在访问旧 IP,而你很可能已经下线了旧机器——这就是"灰度切换变成部分用户 404"。把 TTL 降到 60 s 后,最坏生效时间从 86400 s 压到 60 s,代价是解析器查询量上升 1440 倍,所以必须在切换完成后调回去。

提示

面试话术:“TTL 不是’什么时候生效’,而是’多久之内一定生效’。调小 TTL 不是为了加快生效,而是为了给最坏情况设上界。“这句话能直接压住"TTL 就是缓存时间"这种一句话题。

19.2 DNS 报文结构与记录类型

19.2.1 报文的五个区段

DNS 报文固定 12 字节头(Header),之后是四个可变长区段。面试常考标志位与"哪些区段会变长”:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|            ID (16 bit,把应答与查询配对)                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|QR| Opcode  |AA|TC|RD|RA|   Z    |         RCODE (4 bit)      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     QDCOUNT(问题数,通常 1)  |     ANCOUNT(回答数)          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     NSCOUNT(权威数)          |     ARCOUNT(附加数,EDNS0 在此)|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 区段顺序:Question → Answer → Authority → Additional(后三段可为空)
 域名压缩:重复出现的域名用指针 0xC0xx 指回报文前面,故域名不可任意长
标志位含义排障时说明什么
QR0 查询 / 1 应答抓包时区分方向;两者 ID 必须一致,否则说明混了并发查询
AAAuthoritative Answer为 1 表示来自该域权威服务器;问公共解析器时通常为 0
TCTruncated为 1 表示应答被截断,客户端必须改用 TCP 重查
RD / RARecursion Desired / AvailableRD=1 是客户端请求递归;RA=0 说明它是纯权威,不能当解析器用
RCODE0 NOERROR、2 SERVFAIL、3 NXDOMAIN、5 REFUSEDSERVFAIL 常见于 DNSSEC 校验失败或权威不可达;REFUSED 是被策略拒绝

TC 与 EDNS0 的关系是高频追问:传统 DNS 的 UDP 应答上限 512 字节(RFC 1035),超了就置 TC=1;EDNS0(RFC 6891)通过一条**伪记录(OPT RR)**把 UDP 承载能力协商到更大(常见 1232~4096 字节),并同时承载 DNSSEC 的 DO 位。所以”dig 看到没截断",往往是因为对方支持 EDNS0,而不是记录真的小。

19.2.2 记录类型与"为什么有些记录不能共存"

类型语义面试要说的点
A / AAAA名字 → IPv4 / IPv6一个名字可挂多条 A 形成轮询;双栈域名有问题先看 AAAA
CNAME别名 → 规范名只允许指向另一个名字而非 IP;解析器会继续追到底
NS该域由哪些服务器负责委托(delegation)的载体
MX邮件交换,带优先级标准上不能指向 CNAME
TXT任意文本现用于 SPF/DKIM/DMARC 与域名所有权校验
SOA区的起始授权含序列号与 minimum(负缓存时长)
PTRIP → 名字(反向解析)在 in-addr.arpa/ip6.arpa 下;邮件被拒收常因缺 PTR

为什么 CNAME 不能与 MX 共存、不能用在根域名? 根因是同一条记录集(RRset)里"名字到值"的映射必须唯一。DNS 是一条"问名字、拿值"的查询协议;当某个名字被声明为 CNAME 时,它只剩下别名这一种语义,RFC 1034 要求该名字下不得再存在其他类型的数据,否则解析器拿到 CNAME 后去追新名字,其他记录就成了永远取不到的孤儿。所以 example.com CNAME target.com 与 example.com MX mail.example.com 冲突。根域名(zone apex)不能是 CNAME,因为根域名必须承载 NS 与 SOA(区的委托点与起始授权)——一个名字既然已有 NS/SOA,就不可能"只剩 CNAME 语义"。这就是"裸域不能直接 CNAME 到 CDN"的规范来源,工程上用 A/AAAA 指向 CDN 固定 IP,或用厂商的 ANAME/ALIAS 类记录(服务端合成 A 记录)绕过。同理 MX 指向 CNAME 也违反 RFC 2181 的澄清,它既多一次解析,又给邮件投递路径增加失败面。追踪一条 CNAME 链则意味着解析器逐跳再查,每跳都可能是一次额外迭代:

1
2
3
 www.example.com          CNAME  example.cdn-provider.net   ← 第 1 跳(权威返回)
 example.cdn-provider.net CNAME  edge-42.cdn.net            ← 第 2 跳
 edge-42.cdn.net          A      203.0.113.42               ← 第 3 跳才拿到 IP

每跳都增加延迟(未缓存即为一次 RTT),并放大失败面:任一跳的权威不可达,整个解析就是 SERVFAIL。这是 CDN 接入时建议减少 CNAME 层数的原因。

19.3 DNS 负载均衡与健康检查

策略做法适用与代价
轮询(round robin)同一名字返回多条 A,解析器轮换取用实现最简;粒度是"客户端"而非"请求",无法按负载分配
地理就近(geo-steering)按解析器 IP(EDNS Client Subnet 可带客户端网段)选最近机房依赖解析器位置准确;公共解析器集中化会让"就近"偏到大城市
加权(weighted)给记录配权重,按比例返回常用于灰度;权重只在解析时刻生效,不反映真实负载

分工上,DNS 做的是最外层、最粗粒度的一次分流:决定流量进哪个机房/集群入口。进入机房后,LVS 一类四层负载均衡按连接分流,Nginx/Envoy 再按请求分流与路由(并发模型见第 7 章 7.5 节)。三层职责不可互换:DNS 看不到连接,四层看不到 URL。

小心

不要答"用 DNS 做负载均衡就可以了"。DNS 有两个结构上无法消除的缺陷:(1) 摘除不及时——客户端与中间解析器按 TTL 缓存结果,一台机器挂了,在 TTL 过期前仍有流量打到它;(2) 感知不到服务端真实负载——返回哪条记录与你的 CPU、队列深度无关,DNS 协议里根本没有回传负载的字段。

工程上的补救是分层兜底:DNS 负责机房级选择(TTL 设小一些,如 60 s),健康检查在 DNS 侧只做服务器级存活探测(失败则摘除该 A 记录),精细负载交给机房内的四层/七层,再由应用层做熔断降级(见第 35 章)。能把"DNS 只能做粗粒度、且摘除受 TTL 约束"说清楚,就已超过只背"轮询/加权/就近"的答法。

19.4 dig 的实战用法与 nslookup 的差距

dig(domain information groper)的价值在于它把报文的每个区段都打出来,包括 flags、QUERY/ANSWER/AUTHORITY 段与耗时。写排障命令时,注释要写"为什么这么查":

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 只看答案:脚本里取 IP 用,避免再去解析 "ANSWER SECTION" 这类人类可读文本
dig +short www.example.com

# 走完整迭代链路:跳过本地解析器缓存,复现"根→TLD→权威"每一步,
# 用来区分"权威配置错了"与"本地解析器缓存了旧值"
dig +trace www.example.com

# 换服务器对比:定位本地 DNS 被污染、缓存旧值或配置了内网 view 的问题
dig @8.8.8.8 www.example.com
dig @ns1.example.com www.example.com +norecurse   # 直问权威,绕开所有缓存

# 验证 TC 截断与 EDNS0 协商:人为压到 512 字节逼出 TC=1,再看 TCP 重查是否成功
dig +bufsize=512 www.example.com
dig +tcp www.example.com
dig +noedns www.example.com      # 关闭 EDNS0,回退到 512 字节口径

# 反查与其他类型:PTR 缺失是邮件被拒收的高频原因
dig -x 203.0.113.42 +short
dig MX example.com +short
dig NS example.com +short        # 委托链要逐级向上问才能确认完整

dig 与 nslookup 的差距在于信息量:nslookup 是历史工具,输出经过"友好格式化",默认不显示 flags、TTL、AUTHORITY 段,也不区分答案来自缓存还是权威,只能告诉你"解析出来了/没解析出来"。而排障需要的是为什么:

需求dignslookup
看 AA/TC/RA 标志位头部直接显示不显示
看 TTL(判断缓存新旧)ANSWER 段每条都有不显示
看 AUTHORITY/ADDITIONAL 段完整输出不输出
复现迭代链路+trace 内建无
指定端口、EDNS0、超时控制-p、+bufsize、+time能力缺失

注意

面试官常追问"为什么 nslookup 不推荐"。标准答案不是"它旧",而是它隐藏了判断缓存与截断所必需的信息:同样是"解析成功",dig 能告诉你答案来自哪台服务器、TTL 还剩几秒、是否走过 TCP,nslookup 只能给你一个 IP。

19.5 CDN:回源、命中率与调度

设 命中率 = 边缘直接响应的请求数 / 总请求数,其补数是回源率。回源请求要额外走一次"边缘 → 源站"的 RTT 并占用源站容量,所以回源率高的 CDN 等于给源站加了一层绕路。

1
2
3
4
5
6
7
8
9
                    ┌──────────── 边缘节点 ────────────┐
 用户 ─▶ DNS 调度 ─▶│ 缓存查询 → 命中 → 直接返回        │
        (或 Anycast)│        └ 未命中 ──┐               │
                    └───────────────────┼───────────────┘
                                        │ 回源(长连接 + 协议优化)
                                        ▼
                                   ┌─────────┐
                                   │  源站   │
                                   └─────────┘

命中率低的常见原因(面试高频,按出现频率排序):一是 Cache-Control: no-cache/no-store/private 写错,边缘只能遵守源站指令;二是 URL 带随机参数(?t=时间戳、?rand=),每次都是新 key,等于关掉缓存;三是 Vary 维度过多(Vary: User-Agent 会把每个 UA 变成独立 key)或按 Cookie 全量区分;四是缓存键设计过细,把完整 query string 都算进 key 而业务只需要其中一两个参数;五是 TTL 过短,或源站 max-age 与边缘默认 TTL 冲突时取了小值;六是内容本来就动态(个性化接口),此时应走动态加速而不是硬缓存;七是发布后大规模刷新使缓存未回温,或请求合并没有开启导致同一文件被并发回源多次。

调度有两条路线。DNS 调度按解析器位置/运营商返回不同边缘 IP,粒度是客户端级且受 TTL 约束,切换与摘除都慢。Anycast 让同一 IP 在全球多节点公告,由 BGP 选最近入口,逐包生效、切换快,但路由抖动会影响长连接,且故障节点若未撤公告会持续收流量。两者常叠加:Anycast 做入口就近,DNS 做机房级容量调度。预热(preheat)与刷新(purge) 方向相反,面试常混:预热是主动把内容推到边缘(上线前拉一遍热门文件,避免上线瞬间全量回源打垮源站);刷新是主动让边缘副本失效(发布新版本后强制回源)。刷新本质上牺牲命中率换一致性,因此发布后短时间内回源率会尖峰上升。至于动态加速,对不可缓存的请求,CDN 的价值从"缓存"转为"路径优化":边缘与源站之间保持长连接复用(省掉每次 TCP + TLS 握手,见第 20 章 20.5 节)、使用更优的传输协议(如基于 UDP 的 QUIC,见第 15 章)、选更优的回源路径。这时该看回源时延与回源成功率,而不是命中率。

19.6 一次"网站访问慢"的分层排查法(主线案例)

排障第一原则是只改变一个变量。先用一条命令把一次 HTTP 请求切成若干时间段,再决定往哪一层深挖:

1
2
3
4
5
6
7
# 用 curl -w 把一次请求切成若干时间段;关掉连接复用,否则"第二次很快"
# 可能只是连接被复用了,反而掩盖了真正的握手问题
curl -w '\n time_namelookup:%{time_namelookup} time_connect:%{time_connect}\n
 time_appconnect:%{time_appconnect} time_pretransfer:%{time_pretransfer}\n
 time_starttransfer:%{time_starttransfer} time_redirect:%{time_redirect}\n
 time_total:%{time_total} http_code:%{http_code} remote_ip:%{remote_ip}\n
 num_connects:%{num_connects}\n' -o /dev/null -s https://www.example.com/
时间段(差值)含义异常阈值(同城口径)异常时的根因清单
time_namelookup从发起到 DNS 解析完成> 100 msresolv.conf 指向的解析器不可达或跨网;CNAME 链过长;负缓存/权威抖动;nsswitch.conf 里 dns 排在 files 前多走一次
time_connect − time_namelookupTCP 三次握手耗时> 3 × RTT 或 > 50 ms服务端 SYN 队列满(tcp_max_syn_backlog/somaxconn);中间设备丢包;跨地域(先量 RTT);SYN 重传
time_appconnect − time_connectTLS 握手耗时> 2 × RTT 或 > 100 msTLS 1.2 的 2-RTT 握手(对比见第 20 章 20.5 节);证书链过大、未启用会话复用、OCSP 未装订;CPU 算力不足
time_pretransfer − time_appconnect握手完成到即将发请求接近 0明显偏大说明在等代理或重定向链,或客户端自己在忙
time_starttransfer − time_pretransfer发出请求到收到首字节,即 TTFB> 200 ms应用层慢(下游依赖、慢 SQL、锁);服务端排队(后端并发已满);CDN 回源(边缘未命中);不是网络问题
time_total − time_starttransfer收到首字节到传完与响应体大小/带宽比对带宽瓶颈;丢包导致拥塞窗口上不去;响应体未压缩;最后一公里差

按图索骥的命令序列,不要一上来就抓包:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# 1 DNS 耗时与来源:换解析器对比,若公共解析器快则问题在内网解析器
dig +stats www.example.com | sed -n '/Query time/p;/SERVER/p'
dig @8.8.8.8 +short www.example.com

# 2 建连:nc -vz 只判断端口,telnet 能再看应用是否有应答;-w 防止排障命令自己挂住
nc -vz -w 3 www.example.com 443

# 3 多次采样看抖动:单次数字会被偶发抖动骗,P99 才是用户感知
for i in $(seq 1 10); do
  curl -o /dev/null -s -w '%{time_connect} %{time_appconnect} %{time_starttransfer} %{time_total}\n' \
       https://www.example.com/
done

# 4 对比不同源:换 IP 明显变快说明问题在特定入口而不在应用
curl -o /dev/null -s -w '%{time_starttransfer}\n' --resolve www.example.com:443:203.0.113.42 \
     https://www.example.com/

# 5 TTFB 异常时用 Host 头直连源站:直连快而走 CDN 慢,问题在 CDN 调度或回源路径
curl -o /dev/null -s -w '%{time_starttransfer}\n' -H 'Host: www.example.com' https://203.0.113.9/

重要

time_starttransfer 偏大而前三段正常,是应用层问题不是网络问题。这条判断能在面试中直接止损:很多人的错误回答是把 TTFB 高也归为"网络慢",然后一路去查路由和丢包,方向从一开始就错了。

19.6.1 curl -w 全部时间指标表

变量起点 → 终点与前一指标的差值含义典型阈值(同城口径)
time_namelookup命令起始 → DNS 解析完成纯 DNS 耗时< 50 ms;> 100 ms 异常
time_connect命令起始 → TCP 建连完成−namelookup = 握手 RTT≈ 1 RTT;同城 0.1~0.5 ms 量级
time_appconnect命令起始 → TLS 等握手完成−connect = TLS 握手TLS 1.3 ≈ 1 RTT,TLS 1.2 ≈ 2 RTT
time_pretransfer命令起始 → 即将发出请求−appconnect ≈ 0明显大于 0 说明有代理/重定向/客户端阻塞
time_starttransfer命令起始 → 收到首字节−pretransfer = 服务端处理 + 首包回程 = TTFB< 200 ms;> 500 ms 需查应用
time_redirect所有重定向(含 DNS/建连/握手)累计多次跳转的累计耗时存在重定向时非 0,跳数越多越不可控
time_total命令起始 → 全部完成−starttransfer = 内容传输与 size_download / 带宽 相符即可

两个易踩的坑:这些 time_* 是相对命令起点的累计量而非分段量,必须做减法;time_appconnect 在非 TLS 请求(如 http://)下为 0,此时 time_pretransfer − time_connect 也接近 0,不要错误解读为"TLS 没耗时"。

19.7 抓包与协议分析:tcpdump 看什么

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 抓到文件而不是刷屏:抓包本身很贵,先落盘再离线分析(-w 不解析,减少排障动作的干扰)
tcpdump -i eth0 -s 0 -w /tmp/cap.pcap 'host 203.0.113.42 and tcp port 443'

# 只抓握手三包;只抓 SYN 且非 ACK 是判断"谁发起连接"的标准写法
tcpdump -i any -nn 'tcp port 443 and tcp[tcpflags] & (tcp-syn|tcp-ack) != 0'
tcpdump -i any -nn 'tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0'

# 只看 RST/FIN:定位"谁先断的";RST 往往比超时更能说明问题
tcpdump -i any -nn 'tcp[tcpflags] & (tcp-fin|tcp-rst) != 0'

# 按网段与方向过滤,避免把整个网卡的流量都拉进来
tcpdump -i any -nn 'net 10.0.0.0/8 and src host 10.0.1.5'

# 抓 ICMP:验证"ping 不通"到底是没回包还是被策略丢弃
tcpdump -i any -nn 'icmp'

tcp[tcpflags] 的位含义固定:tcp-fin=0x01、tcp-syn=0x02、tcp-rst=0x04、tcp-push=0x08、tcp-ack=0x10、tcp-urg=0x20。

观察对象正常样子异常含义
三次握手SYN → SYN,ACK → ACK 三包齐全,间隔 ≈ 1 RTT只有 SYN 无应答:未监听、SYN 队列满、中间设备丢包;出现 SYN 重传即丢包证据
重传偶发,间隔近似指数退避(RTO 计算见第 17 章 17.2 节)大量重传 = 链路丢包,或拥塞窗口被压到很小
重复 ACK偶尔出现连续三个重复 ACK 提示丢包并触发快速重传
RST正常关闭流程里少见应用进程退出、连接被中间设备重置、端口未监听;“谁发 RST 谁有问题"要结合方向判断
零窗口不应长期出现接收缓冲区被打满 → 应用读取太慢(不是网络问题)
TLS 握手ClientHello 到 Finished 的往返数固定往返数偏多说明未复用会话或证书链过长(见第 20 章 20.5 节)
传输段大小稳定在 MSS 附近(常见 1460)长期小于 MSS 说明路径 MTU 受限、拥塞窗口小或应用写得零碎

警告

“抓包看到慢"不等于"服务端慢”。 抓包点决定了你能看到的因果:在客户端抓,你看到的是"字节到达客户端慢”,它可能来自服务端、可能来自中间链路、也可能来自客户端自己读取慢(零窗口)。要定位责任方,必须在两端同时抓并比对时间戳,或至少先确认接收窗口未被打满。

Wireshark 里两个必用功能:Follow → TCP Stream 把一条连接的双向载荷按序拼成可读流,用来确认应用层到底发了什么(响应体是否被压缩、是否少了某个头部),注意它按连接过滤,多连接时要先在包列表里选中一条会话;Statistics → TCP Stream Graphs 的 Throughput 图给出实际速率随时间的变化,Time-Sequence (Stevens) 图给出序列号随时间上升的斜率——斜率变平就是停等或阻塞,锯齿状回落通常是丢包/重传导致窗口收缩。这两张图是把"抓包"变成"证据"的关键。

19.8 连通性工具的能力边界

面试高频辨析题几乎都围绕"某个工具说通/不通,能不能证明服务可用"。核心原则:每个工具只探测它自己那一层的语义,跨层推断就是错的。

工具实际探测的东西能力边界(关键)替代手段
pingICMP echo request/reply 是否往返显示"通"不保证端口可达;被防火墙丢 ICMP 时显示"不通"但服务完全可用nc -vz/curl 直接测端口;tcpdump icmp 确认丢在哪一侧
traceroute逐跳增大 TTL 收集 ICMP Time Exceeded依赖 ICMP 回包,屏蔽就显示 *;不反映实际转发路径(多路径/负载均衡导致每跳不同)mtr 连续统计;traceroute -T -p 443 用 TCP 探测绕过 ICMP 屏蔽
mtr持续采样的逐跳丢包与时延同样受 ICMP 策略影响;中间跳丢包不代表端到端丢包关注最后一跳的丢包与抖动,而非中间跳
telnet / nc -vzTCP 建连是否成功(nc -z 不发数据)只证明三次握手成功,不证明应用能正确响应;UDP 模式结论不可靠(无握手)curl -v 看应用层响应;openssl s_client 验 TLS
ss本机套接字状态与队列只能看本机;不产生流量,故不验证对端配合 ss -lntp、ss -tn state established 看积压
curl -w端到端各阶段耗时只给时间,不给原因与抓包、服务端日志对齐定位

ICMP 被禁时的替代手段(把"ping 不通"继续往下推):一是 TCP 建连探测,nc -vz -w 3 host port,或 traceroute -T -p 443 host 用 TCP SYN 探路;二是应用层探测,curl -v --max-time 5 看是否拿到状态码,openssl s_client -connect host:443 -servername host 验证证书链;三是抓包确认责任方,tcpdump -nn 'icmp or tcp port 443' 区分"没发包"、“发了没回”、“回了被丢”;四是绕开本机网络栈干扰,mtr --tcp --port 443 host、多网卡用 -a/-I 指定源地址对比。

小心

把 traceroute 输出里的 * 当成"这一跳坏了"是最常见的错误回答。* 只是"这一跳没有回 ICMP 超时报文",很多骨干设备就是这么配的;真正有意义的是从某一跳开始丢包率持续到最后一跳。面试官追问"那你怎么判断"时,正确答案是"看 mtr 的最后一跳与端到端丢包,而不是中间跳"。

本章小结

关键结论一句话表述常见错误
递归与迭代的分界客户端→本地 DNS 是递归(RD=1),本地 DNS→根→TLD→权威是迭代(RD=0),根服务器从不做递归把整条链说成"一层层递归问下去"
缓存优先级/etc/hosts 与 DNS 的先后由 nsswitch.conf 的 hosts: 行决定认为 hosts 永远第一,忽略应用自身的 DNS 缓存
负缓存NXDOMAIN 会按 SOA 的 minimum 被缓存,“新域名解析不出来"不一定是配置错把 NXDOMAIN 一律当成"记录没配”
TTL 的作用TTL 是"最坏生效时间的上界";切换前调小、切换后调回直接改 IP 而 TTL 仍是 86400,部分用户长期打到旧机
CNAME 的限制名字一旦是 CNAME 就不能承载其他类型;zone apex 必须有 NS/SOA说"根域名 CNAME 只是不推荐",把规范约束说成风格问题
DNS 负载均衡的缺陷摘除受 TTL 约束、无法感知服务端真实负载,只能做机房级粗粒度分流答"用 DNS 做负载均衡就够了"
TC 与 EDNS0UDP 传统上限 512 字节,超限置 TC=1 必须改 TCP 重查;EDNS0 扩大承载并承载 DO 位把 TC=1 解释成"服务器过载"
慢的分层定位用 curl -w 的差值把问题切到 DNS/建连/TLS/TTFB/传输五段,再进对应工具一上来就抓包;把 TTFB 高归因为"网络慢"
抓包的局限单点抓包只证明"字节到达该点的快慢",不能直接判定责任方由"抓包看到慢"直接得出"服务端慢"
工具边界ping 通不代表端口通,traceroute 的 * 不代表该跳故障,telnet 通不代表应用正常用 ICMP 结论推断应用可用性

自测题

Q1(推导)某域名的 A 记录 TTL 为 86400,你计划在明天 10:00 把服务从旧机房切到新机房。请给出完整的切换步骤与每一步的等待时长依据,并说明不做这一步会发生什么。

答案与解析

标准答案是两阶段 TTL 收敛:(1) 立即把 TTL 从 86400 改为 60,然后等待至少 86400 s(24 h),让所有仍缓存旧的长 TTL 的解析器过期,等待时长的依据是"旧 TTL 是上界",不能缩短;(2) 在 10:00 把 A 记录指向新机房,等约 60 s 让新值扩散,观察新机房入口流量上升;(3) 确认旧机房流量归零后下线旧机器,再把 TTL 调回 86400。

如果不做第 (1) 步就直接改:最坏情况下,那些在改之前刚查过记录的解析器会继续用旧 IP 长达 24 小时,表现为"一部分用户访问到已下线的旧机房",比例随缓存分布抖动,极难复现。

错在哪:常见错误是答"把 TTL 设成 0 就能立刻生效"——TTL=0 只表示"不要复用",多数解析器有秒级下限,客户端缓存(浏览器、JVM、Go 的 DNS 缓存)更不受你的 TTL 支配。另一个错误是只答"提前调小 TTL"而说不出要等多久,面试官追问"提前多久"就是在验证你是否知道 TTL 是上界而非周期。追问方向:调小 TTL 的代价是什么(解析器查询量上升、权威侧压力变化)。

Q2(辨析)同事说:"example.com 的裸域我直接加一条 CNAME 指向 CDN 就行了。“请指出这条方案的问题,并给出两种可落地的替代。

答案与解析

问题:example.com 是区的顶点(zone apex),按 RFC 1034/1035 它必须承载该区的 NS 与 SOA;而一旦某个名字被声明为 CNAME,别名语义就独占该名字,不允许存在其他类型的数据。二者冲突,所以裸域不能是 CNAME。同理可解释"CNAME 不能与 MX 共存”:example.com 若既是 CNAME 又挂 MX,解析器追到别名后,原名字上的 MX 就永久取不到。

两种替代:(1) 用 A/AAAA 记录直接指向 CDN 提供的固定入口 IP,代价是 IP 变更需跟随;(2) 使用厂商提供的类 CNAME 顶点别名记录(叫法各异,如 ANAME/ALIAS),由解析服务端在应答时把目标解析成 A 记录再返回,从而不违反规范地实现"裸域走 CDN"。

错在哪:把规范约束当成"厂商限制"或"不推荐",回答里说不出"zone apex 必须有 NS/SOA"这个根因。追问方向:MX 指向 CNAME 为什么也被禁止?正确答案是规范要求 MX 的目标不应是 CNAME,它引入额外解析跳数与失败面;进一步还会问你 CNAME 链每一跳对 RTT 与 SERVFAIL 风险的影响。

Q3(计算)一次 curl -w 输出为 time_namelookup=0.004、time_connect=0.052、time_appconnect=0.158、time_pretransfer=0.159、time_starttransfer=1.310、time_total=1.340(秒,--resolve 直连某机房,http_code=200,响应体 12 KB)。请把总耗时拆成五段,指出问题在哪一层,并给出下一步两个验证动作。

答案与解析

分段(全部做减法):

1
2
3
4
5
6
 DNS:      0.004 s                     → 4 ms,正常
 TCP 建连: 0.052 − 0.004 = 0.048 s     → 48 ms,偏高(同城 RTT 应为 0.1~0.5 ms 量级)
 TLS 握手: 0.158 − 0.052 = 0.106 s     → 106 ms,偏高
 应用等待: 0.159 − 0.158 ≈ 0.001 s     → 可忽略
 TTFB:     1.310 − 0.159 = 1.151 s     → 1.15 s,异常,占总耗时 86%
 内容传输: 1.340 − 1.310 = 0.030 s     → 12 KB / 30 ms ≈ 400 KB/s,偏低但不主导

结论:瓶颈在 TTFB(服务端处理时间);建连与 TLS 偏高提示链路 RTT 偏大(可能跨地域或路径绕行),但不是主因。

下一步验证:(1) 用 -H 'Host: ...' 直连源站(绕过 CDN)对比 time_starttransfer,若直连快则问题在 CDN 回源或调度;(2) 在服务端对齐同一时刻的应用日志/链路追踪,判断是下游依赖、慢查询还是排队,同时用 mtr --tcp --port 443 确认 48 ms 的建连是否来自跨地域路由。

错在哪:最常见的是"总耗时 1.34 s,网络慢",把 TTFB 也算进网络;另一个是把 time_appconnect 直接当 TLS 耗时(它是相对命令起点的累计量,必须减去 time_connect)。追问方向:如果 time_total − time_starttransfer 很大而 TTFB 正常,你会怀疑什么?答案是带宽瓶颈、丢包导致拥塞窗口受限、响应体未压缩,或最后一公里质量差。

Q4(判断)“我用 ping www.example.com 能通,说明 443 端口是开的、网站是好的。“请判断并说明,同时给出这个结论会漏掉的三类故障。

答案与解析

判断:错。ping 只证明 ICMP echo 有去有回,即"该主机在 IP 层可达”,完全不涉及 TCP 端口、监听状态与应用健康。反向也不成立:ping 不通而服务完全可用是常见情形,因为很多安全组/防火墙只放行所需的 TCP 端口而显式丢弃 ICMP。

会漏掉的三类故障:(1) 端口未监听或被策略拒绝——ping 通但 443 无监听,SYN 得到 RST;或防火墙 DROP,表现为 SYN 重传后超时;(2) 应用层异常——端口开着但返回 502/503 或响应体错误;(3) TLS 层异常——证书过期、SNI 与证书不匹配、证书链缺中间证书,此时 TCP 通而 HTTPS 请求失败。

正确做法是分层验证:nc -vz -w 3 host 443 验证端口 → curl -v --max-time 5 https://host/ 验证应用与 TLS → openssl s_client -connect host:443 -servername host 单独验证证书链。若 ping 不通而业务正常,先确认 ICMP 是否被策略屏蔽(tcpdump -nn icmp 看是否根本没回包),不要急着改网络。

追问方向:telnet host 443 成功说明了什么?——只说明 TCP 三次握手完成(见第 16 章 16.1 节),不说明应用能正确应答,也不说明 TLS 能协商成功。

Q5(设计)线上告警"某地域用户访问慢”,该地域用户走 CDN。请设计一套从 DNS 到 CDN 到源站的排查顺序,写明每一步的命令、要看的指标,以及"看到什么就跳到下一步"的判据。

答案与解析

按"由外向内、由粗到细"排序,每步都有明确跳转判据:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
 步骤 1  DNS 调度是否正确(先确认用户被调度到哪个机房)
         dig +short www.example.com          # 从该地域解析器视角取结果
         dig @<该地域解析器> www.example.com  # 对比是否被调度到远端节点
         判据:返回 IP 不属于就近节点 → 调度问题(查 geo 库 / EDNS Client Subnet);否则进步骤 2
 步骤 2  CDN 边缘时延与命中率(区分"边缘慢"还是"回源慢")
         curl -w '<time_* 模板>' -o /dev/null -s https://www.example.com/
         控制台看该节点命中率与回源率
         判据:命中率高而 TTFB 高 → 边缘或最后一公里;命中率骤降 → 进步骤 3
 步骤 3  回源路径(边缘到源站)
         边缘侧看回源时延与成功率;curl -H 'Host: ...' 直连源站对比
         判据:直连源站快而走 CDN 慢 → 回源路径差或长连接复用失效(每次回源重建 TCP+TLS)
 步骤 4  源站与应用(TTFB 归因)
         服务端看应用耗时、下游依赖、慢查询、线程池队列
         判据:源站自身 TTFB 正常 → 回到链路;源站 TTFB 高 → 应用问题,与网络无关
 步骤 5  路径质量与丢包(仅当步骤 1~4 都正常)
         mtr --tcp --port 443 -c 100 <边缘IP>     # 只看最后一跳的丢包与抖动
         tcpdump -i any -nn -w cap.pcap 'host <边缘IP> and tcp port 443'
         判据:末跳丢包/抖动持续 → 链路质量问题;否则两端抓包比对时间戳

设计要点(也是加分点):不要从步骤 5 开始,抓包成本最高且最难归因,只在时间指标已把范围收窄后才用;所有时间结论都要多次采样(单次会被抖动骗);并且必须跨地域对比——“某地域慢"本身意味着需要一个对照组的数字,否则无法区分"该地域的网络问题"与"全网都慢只是这个地域先被报出来”。

错在哪:把"用户慢"直接当成"CDN 慢"或"源站慢",跳过 DNS 调度这一步,而调度错误(用户被指到远端节点)恰恰是"某地域慢"最常见的原因。追问方向:如果该地域用户全被调度到同一个远端节点,你先怀疑什么?优先怀疑解析器位置失真(用户使用集中化的公共解析器,geo 判断基于解析器而非用户)与 EDNS Client Subnet 未启用或被剥离。

面试高频问题与陷阱

面试问法考查角度陷阱 / 加分点对应本章小节
请完整讲一下 DNS 的解析过程,递归和迭代分别发生在哪一段?是否把两段责任划分讲清错误回答:把浏览器缓存、根服务器、权威服务器串成一条"递归链",说"根服务器帮我递归查到结果"——根服务器从不做递归;加分:点出 RD/RA 标志位与本地 DNS 服务器是分界点19.1.1
为什么 CNAME 记录不能和 MX 记录共存?为什么根域名不能用 CNAME?对 RRset 语义与区顶点约束的理解错误回答:“因为厂商不支持"或"只是不推荐”——说不出"名字一旦是 CNAME 就不能承载其他类型"以及"zone apex 必须有 NS/SOA";加分:给出 ANAME/ALIAS 这类工程替代19.2.2
我们准备把服务切到新机房,为什么上线前要先把 TTL 调小?是否理解 TTL 是"最坏生效上界"错误回答:“TTL 是缓存时间,调小能快点生效”,但说不出要提前多久(必须是旧 TTL 的时长)与调小 TTL 的代价(解析器查询量上升);加分:给出 86400 → 60 的算例19.1.3
用 DNS 做负载均衡有什么问题?是否知道 DNS 的固有缺陷错误回答:“轮询/加权/就近就够了”——说不出摘除受 TTL 约束、DNS 协议里没有回传服务端负载的字段;加分:说明 DNS 只做机房级粗粒度分流,精细负载交给四层/七层19.3
ping 通是不是说明服务就是好的?traceroute 里出现 * 是不是说明那一跳坏了?工具能力边界,两个问题常连着问错误回答:用 ICMP 结论推断端口/应用可用;把 * 当故障跳。加分:说明 ICMP 常被策略丢弃、* 只表示该跳不回超时报文,真正有意义的是末跳丢包19.8
用户反馈访问慢,你怎么排查?是否有分层方法论而非乱试错误回答:“先抓包看看"或"重启一下”——没有把问题先切到 DNS/建连/TLS/TTFB/传输五段;加分:给出 curl -w 差值定位法与每段根因清单,并强调多次采样19.6
抓包看到响应很慢,能说明是服务端慢吗?因果与观测点错误回答:“抓包慢就是服务端慢”——忽略了抓包点、中间链路与客户端接收窗口;加分:说明必须在两端同时抓并比对时间戳,或先确认接收窗口未为零19.7
time_appconnect 是 0,是不是说明 TLS 握手没有耗时?指标口径错误回答:“是,TLS 很快”——实际 time_* 是累计量,且非 TLS 请求该值为 0;加分:说明所有 time_* 都要做减法,并区分 TLS 1.2 与 1.3 的往返数19.6.1
CDN 命中率突然下降,你会从哪几个方向查?对缓存键与源站指令的理解错误回答:“多加点机器"或"把 TTL 调长”——说不出 URL 随机参数、Vary/Cookie 维度、Cache-Control 指令、请求合并;加分:区分"缓存策略失效"与"内容本就动态"(该走动态加速)19.5
为什么 dig 比 nslookup 更适合排障?工具输出的信息量错误回答:"dig 更新更现代"——说不出 nslookup 不显示 flags/TTL/AUTHORITY 段,因此无法判断答案来自缓存还是权威;加分:提到 +trace、+tcp、+bufsize 才能复现截断与迭代链路19.4

延伸阅读

  • RFC 1034《Domain names - concepts and facilities》:递归/迭代、委托与 zone 概念的第一手定义,19.1 节的路径划分以它为准。
  • RFC 1035《Domain names - implementation and specification》:报文格式、512 字节 UDP 上限与各记录类型的字段定义。
  • RFC 2181《Clarifications to the DNS Specification》:CNAME 与 MX/NS 目标关系的澄清,19.2.2 节的规范依据。
  • RFC 2308《Negative Caching of DNS Queries》:负缓存机制与 SOA minimum 的口径。
  • RFC 6891《Extension Mechanisms for DNS (EDNS(0))》:EDNS0 伪记录、UDP 承载能力协商与 DO 位。
  • RFC 8499《DNS Terminology》:术语标准化,写文档与答辩时统一用词。
  • man 1 dig、man 8 tcpdump、man 5 nsswitch.conf、man 5 resolv.conf:本章命令行为与配置文件优先级的权威口径。
  • Wireshark 官方文档中 Statistics → TCP Stream Graphs 一节:吞吐图与 Stevens 时间序列图的读法。