深夜加班完,你特别想吃那家朋友反复推荐的“阿婆面馆”。朋友只说过店名,没说门牌号。你站在街口,怎样才能找到这家店?你不可能把全城每条街翻一遍——于是你顺着直觉走:先在自己的记忆里翻(刚才路过是不是见过?),没印象;再翻手机里存过的地址小抄;还是没找到,只好打电话给城市查号台。接线员没有直接给你答案,而是替你去问:先问总机(这家店归哪个部门管?),再问餐饮协会(属于哪家连锁?),最后问到阿婆面馆总店登记处,终于拿到门牌号,回来告诉你。 这套“找人问路”的接力,就是 DNS 解析流程的日常模样。当你敲下一个网址,浏览器、操作系统、本地解析器、根服务器、顶级域服务器、权威服务器,就像记忆、小抄本、接线员、总机、协会和总店一样,一层层把“店名”翻译成“门牌号”——也就是 IP 地址。本文就带你顺着这条链路完整走一遍,顺便看看怎么防止有人在半路给你指错方向(DNS 劫持)。

岗位关联

理解 DNS 解析流程是排查网络故障、设计高可用服务与防御域名劫持的基石,也是后端与基础设施工程师面试的高频考点,掌握它能让你的系统排障能力直接上一个台阶。

目录
  1. 可视化演示
  2. 域名与 IP 地址:店名与门牌号
  3. 缓存查询链路:记忆、小抄与便利贴
  4. 本地解析器:全权代办的接线员
  5. 根域名服务器:只记分类的总机
  6. 顶级域与权威服务器:协会与总店登记处
  7. 资源记录类型:查号台的不同词条
  8. DNS 安全与 DoH:把明信片装进信封
  9. 完整解析流程:从输入网址到开门进店
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

\u5c55\u793a\u5b57\u7b26\u4e32 Hello \u4e2d\u6bcf\u4e2a\u5b57\u7b26\u5982\u4f55\u901a\u8fc7ASCII\u7f16\u7801\u6620\u5c04\u4e3a\u5341\u8fdb\u5236\u3001\u5341\u516d\u8fdb\u5236\u548c\u4e8c\u8fdb\u5236\u8868\u793a\uff0c\u9010\u5b57\u7b26\u626b\u63cf\u5c55\u793a\u7f16\u7801\u8f6c\u6362\u8fc7\u7a0b\u3002

本节配有交互动效(进入视口后自动加载,约 3KB)。

ASCII\u5b57\u7b26\u7f16\u7801 \u5b57\u7b26 H \u2192 ASCII 72 \u5f53\u524d\u626b\u63cf\u4f4d\u7f6e char\u2192dec\u2192hex\u2192bin \u5b57\u7b26\u5230\u4e8c\u8fdb\u5236\u7f16\u7801\u8f6c\u6362

域名与 IP 地址:店名与门牌号

阿婆面馆有两套身份:挂在门口的招牌,和写在房产证上的门牌号。人脑擅长记“阿婆面馆”,导航软件却只认“某某路 37 号”。互联网同样如此——你习惯敲 www.example.com,而路由器、交换机只认 93.184.216.34 这样的 IP 地址。DNS(域名系统)就是这座城市里最庞大的“店名与门牌号翻译服务”,负责把人类语言翻译成机器坐标。

但域名并不是随手起的乱码,它像“城市·商圈·店号”一样有严谨的层级。你从右往左读:com 像“市”,example 像“商圈”,www 像具体的店号。DNS 在解析时也正是从最右侧开始,一层层缩小范围,最终锁定位居最左侧的具体主机。

技术上,一个完整域名叫 FQDN(完全限定域名),由多个标签(label)用点分隔,总长不超过 253 字符,每个标签不超过 63 字符。DNS 报文默认走 UDP 的 53 端口(数据量大时切换 TCP),报文头部用 16 位 ID 把“问题”和“应答”一一配对。每个标签前面带一个长度字节,像快递单上分段填写的地址栏。

所以当你向查号台报出“阿婆面馆”时,接线员并不是在通讯录里从头翻到尾,而是先锁定“市”,再锁定“商圈”,最后精确到“店”。域名层级就是为这种“逐级缩小”而生的。

www.example.com  ⇒  com⏟顶级域⋅example⏟二级域⋅www⏟主机名\text{www.example.com} \;\Rightarrow\; \underbrace{\text{com}}_{\text{顶级域}} \cdot \underbrace{\text{example}}_{\text{二级域}} \cdot \underbrace{\text{www}}_{\text{主机名}}
域名从右向左逐级变具体:顶级域是“市”,二级域是“商圈”,主机名是“店号”。DNS 解析也是这个顺序。
  1. 顶级域 com最右侧是范围最大的分类,类似“这座城市”。互联网上 com/net/org 都算顶级域。
    com⏟顶级域=城市\underbrace{\text{com}}_{\text{顶级域} = \text{城市}}
  2. 二级域 example在注册商处购买的专属名字,类似“商圈”或者“街区”,是某组织拥有的地盘。
    example⏟二级域=商圈\underbrace{\text{example}}_{\text{二级域} = \text{商圈}}
  3. 主机名 www最左侧是具体的一台机器,类似面馆门口挂招牌的那间铺位。
    www⏟主机名=店号\underbrace{\text{www}}_{\text{主机名} = \text{店号}}
  4. 整体读法把三块拼起来,从右往左读一遍就是解析器的工作路径:先确定城市,再找商圈,最后点名店铺。
    www⋅example⋅com  ⇒  逐级缩小范围\text{www} \cdot \text{example} \cdot \text{com} \;\Rightarrow\; \text{逐级缩小范围}

缓存查询链路:记忆、小抄与便利贴

你站在街口,第一反应是回忆自己以前有没有路过这家店——这就是浏览器缓存,最快但容量最小。没想起来?再翻手机备忘录——操作系统缓存,容量大一些。还没找到?最后看一眼冰箱上那张手写便利贴——HOSTS 文件,管理员手动指定的特殊地址。这三层全部落空,你才舍得麻烦查号台。

缓存的最大问题是“过期”。面馆可能搬了家,但你小抄上还写着老地址。DNS 用 TTL(生存时间)给每条记录贴上保质期:从存进缓存的那一刻开始倒计时,归零后必须重新查询,否则就会拿着旧门牌号白跑一趟。

TTL 由权威服务器在下发记录时决定,常见取值从 60 秒到 86400 秒。值太小会让每次访问都穿透到全链路、增加压力;值太大则域名更换 IP 后,用户要等很久才能拿到新地址。工程上常用“迁移前调低 TTL,迁移完成再调高”的手法,让旧缓存尽快过期。

一层层小抄就像保鲜期不同的便利贴:越靠近用户的缓存查得越快,但也越可能保存着“搬家前”的旧地址。这也是为什么“改了 DNS 却迟迟不生效”——总有人手里还攥着没过期的旧门牌。

t剩余有效=TTL0−(t当前−t存入)t_{\text{剩余有效}} = \text{TTL}_0 - \left(t_{\text{当前}} - t_{\text{存入}}\right)
记录能继续使用的剩余时间 = 原始保质期减去已经过去的时间。归零后缓存作废,必须重新解析。
  1. TTL₀ 原始保质期权威服务器下发记录时给出的存活时间,比如 300 秒,相当于查号台告诉你“这个门牌号 5 分钟内有效”。
    TTL0=300  s\text{TTL}_0 = 300\;\text{s}
  2. t当前 − t存入从记录被写进缓存的时刻到现在过去了多久,相当于你那张小抄写了几天了。
    t当前−t存入=已过去的时间t_{\text{当前}} - t_{\text{存入}} = \text{已过去的时间}
  3. t剩余有效用原始保质期减去已流逝时间。结果小于等于零,就说明便利贴过期,必须重新问路。
    t剩余有效≤0  ⇒  缓存失效, 重新查询t_{\text{剩余有效}} \leq 0 \;\Rightarrow\; \text{缓存失效, 重新查询}

本地解析器:全权代办的接线员

三层小抄全部落空,你只好拨通查号台。你说“帮我找阿婆面馆的地址”,然后挂机等回铃。你只需要拜托一次,接线员会全部搞定——这就是递归查询:客户端把解析任务“一个电话”全权委托给本地解析器,自己不再参与任何细节。

本地解析器通常由运营商通过 DHCP 下发配置,也可以手动指定公共 DNS(如 8.8.8.8、223.5.5.5)。它对外扮演“代理”,对内维护一份大缓存:收到请求先查缓存,命中就直接返回;未命中才向上游逐级询问,并负责失败重试和超时处理。

用户敲一下回车,实际发生了“一次递归 + 若干次迭代”。递归只有一跳(用户到解析器),迭代则是解析器分别向根、顶级域、权威发出的追问。用户体感不到这些细节,只觉得“电话响了一声就接通了”。

接线员把找到的门牌号顺手记在便签本上——这就是解析器缓存。下次任何邻居再问同一家店,她直接念出号码,一秒钟都不浪费。这也是第二次访问同一个网站总是更快的原因。

N总往返=1  (用户→解析器)⏟递归  +  3  (根/顶级域/权威)⏟迭代=4N_{\text{总往返}} = 1\;\underbrace{(\text{用户}\to\text{解析器})}_{\text{递归}}\;+\;3\;\underbrace{(\text{根/顶级域/权威})}_{\text{迭代}}=4
用户视角只有一次递归委托;解析器视角则是三次迭代追问。总数 4 次往返,背后 3 次对用户完全透明。
  1. 递归的 1客户端只发一次请求就挂机等结果,像你只打一个电话给查号台。
    1  (用户→解析器)⏟一次委托1\;\underbrace{(\text{用户}\to\text{解析器})}_{\text{一次委托}}
  2. 迭代的 3解析器分别向根、顶级域、权威各问一次,每次只拿到“下一站去哪”的指引。
    3  (根/顶级域/权威)⏟三次追问3\;\underbrace{(\text{根/顶级域/权威})}_{\text{三次追问}}
  3. 总往返 4把委托和追问加起来,就是一次未命中缓存的完整解析开销。
    N总往返=1+3=4N_{\text{总往返}} = 1 + 3 = 4

根域名服务器:只记分类的总机

接线员开始干活了。她先拨通总机——根域名服务器。总机手里的本子只有一个功能:分类名录。“阿婆面馆?她归餐饮协会管。”总机从来不存具体门店地址,只告诉接线员下一个该去问谁。

根服务器是整个 DNS 树的最顶端,全球共 13 组逻辑标识(A 到 M 根),但背后是数百台镜像节点,通过任播技术分布在世界各地。它的核心资产是“根区文件”:一份列出所有顶级域及其服务器地址的清单,内容只有几百 KB,却撑起整个互联网的寻址起点。

根服务器响应的不是最终 IP,而是“下一站”——顶级域服务器的地址。这种“你去找谁”的应答方式就是迭代查询的精髓:每一层服务器只负责把查询者引导到更接近答案的下一层,自己不替别人跑完全程。

总机从不给出“门牌号”,它只做向导。就像大型商场一楼的总服务台,你问“某店在哪”,它只告诉你“该去三楼餐饮区”,而不是带你走完全程。这正是迭代查询的第一跳。

根区清单:  com.⏟通用顶级域  cn.⏟国家顶级域  ⋯    ⇒  应答:  去问  com  顶级域服务器\text{根区清单:}\;\underbrace{\texttt{com.}}_{\text{通用顶级域}}\;\underbrace{\texttt{cn.}}_{\text{国家顶级域}}\;\cdots\;\;\Rightarrow\;\text{应答:}\;\text{去问}\; \texttt{com}\;\text{顶级域服务器}
根区文件只记录“哪些顶级域存在、归谁管”。根服务器永远只回指针,不回门牌号。
  1. 根区清单根手里只有一张顶级域列表,就像总机手里只有各部门分机表。
    清单:  com., net., org., cn. ⋯\text{清单:}\; \texttt{com.},\ \texttt{net.},\ \texttt{org.},\ \texttt{cn.}\ \cdots
  2. com. 带点结尾根区里顶级域都以点结尾,表示这是域名树的顶层节点。
    com.  ≡  顶级域节点\texttt{com.} \;\equiv\; \text{顶级域节点}
  3. 箭头后的应答根收到 example.com 查询后,返回“去问管理 com 的服务器”,而不是直接给出 IP。
    ⇒  下一站:  com  的 NS 服务器\Rightarrow\; \text{下一站:}\; \texttt{com}\;\text{的 NS 服务器}

顶级域与权威服务器:协会与总店登记处

接线员按总机指引,拨通“餐饮协会”——顶级域服务器(管理 com 的那台)。协会又进一步:它不直接登记每一家面馆,但知道“阿婆面馆归华东总店登记处管”。这个“华东总店登记处”就是权威服务器,只有它手里攥着最终门牌号。

顶级域分两大类:通用顶级域(gTLD,如 com/net/org)和国家顶级域(ccTLD,如 cn/uk),各自由相应注册管理机构运营。而 example.com 的权威服务器,是域名注册商(如阿里云、GoDaddy)在你购买域名时通过 NS 记录为你指定的。

从根到顶级域再到权威,这种“一级管一级、权力逐级下放”的机制叫区域委托。权威服务器是链路的终点站,它手里才有域名与 IP 的最终对应表。只有权威服务器返回 A 记录,整个解析才算真正拿到答案。

协会不存门店地址,总店登记处才存——找错层级就永远拿不到答案。所以排查“解析不出来”时,要先问一句:这一层的委托关系是不是断了(比如 NS 记录被误删)?

root  ▹  com⏟顶级域  ▹  example.com⏟权威区域\text{root} \;\triangleright\; \underbrace{\texttt{com}}_{\text{顶级域}} \;\triangleright\; \underbrace{\texttt{example.com}}_{\text{权威区域}}
三角符号表示“委托”:根把 com 委托给顶级域,顶级域把 example.com 委托给权威。答案最终来自链条末端。
  1. root 委托 com根区文件声明 com 由哪几台顶级域服务器负责,这是第一层委托。
    root  ▹  com\text{root} \;\triangleright\; \texttt{com}
  2. com 委托 example.com顶级域服务器的区域文件里,登记着 example.com 的 NS 记录,指向权威服务器。
    com  ▹  example.com\texttt{com} \;\triangleright\; \texttt{example.com}
  3. 权威区域最底层example.com 的权威服务器保存 A 记录等最终数据,委托链到这里结束。
    example.com⏟权威区域, 最终答案\underbrace{\texttt{example.com}}_{\text{权威区域, 最终答案}}

资源记录类型:查号台的不同词条

查号台的词条不止一种:有人问门店地址(A),有人找招牌背后的正门(CNAME),外卖平台要配送电话(MX),商场物业要找商户负责人(NS)。DNS 的资源记录就是一系列按类型分类的“词条”,每种词条解决一种寻址需求。

A 记录把域名映射到 IPv4 地址,是使用最频繁的类型;CNAME 把别名指向另一个规范域名,常见于让 www 指向裸域名;MX 记录告诉邮件系统“这个域名的信该投到哪台收件服务器”,带优先级数字,数字越小越优先;NS 记录则声明某个子域由哪台服务器授权。

解析器拿到的应答里包含 answer 段,里面就是这些记录。一条查询可能返回多条 A 记录(轮询负载均衡),也可能先返回 CNAME 再附带目标 A 记录。TTL 作为记录的属性一并下发,像快递包裹上附带的保质期标签。

就像同一家面馆你要记不同的电话:订座是一个号、外卖是另一个号、物业是第三个号。DNS 记录类型正是把“这家店的各种联系方式”分门别类地存好,随查随取。

A:example.com→  93.184.216.34CNAME:www→  example.comMX:@(10)→  mail.example.comNS:example.com→  ns1.registrar.com\begin{array}{lll} A:&\texttt{example.com}&\to\; 93.184.216.34\\ \text{CNAME}:&\texttt{www}&\to\;\texttt{example.com}\\ \text{MX}:&\texttt{@}(10)&\to\;\texttt{mail.example.com}\\ \text{NS}:&\texttt{example.com}&\to\;\texttt{ns1.registrar.com}\end{array}
四种常见记录:A 直译门牌号,CNAME 别名指向,MX 邮件投递带优先级,NS 声明区域授权。
  1. A 记录把域名翻译成 IPv4 地址,是“店名到门牌号”的直接映射。
    A:  example.com  →  93.184.216.34A:\; \texttt{example.com} \;\to\; \texttt{93.184.216.34}
  2. CNAME 记录把别名指向规范名,像两块招牌同时指向同一家店的正门。
    CNAME:  www  →  example.com\text{CNAME}:\; \texttt{www} \;\to\; \texttt{example.com}
  3. MX 记录与优先级数字表示优先顺序,越小越先尝试,保证邮件有备用通道。
    MX:  @(10)  →  mail.example.com\text{MX}:\; \texttt{@}(10) \;\to\; \texttt{mail.example.com}
  4. NS 记录声明“这个区域的权威是谁”,是委托链条上的路标。
    NS:  example.com  →  ns1.registrar.com\text{NS}:\; \texttt{example.com} \;\to\; \texttt{ns1.registrar.com}

DNS 安全与 DoH:把明信片装进信封

传统 DNS 查询是“明信片”:53 端口、UDP 明文,沿途任何邮差都能拆开看,还能把“阿婆面馆”偷偷改成“黑心面馆”——这就是 DNS 劫持和缓存投毒。运营商广告注入、钓鱼攻击都喜欢在这道闸口做手脚,因为它在任何网页请求之前发生。

DNS over HTTPS(DoH)把 DNS 查询“装进信封”:查询内容先封装成 HTTPS 请求,走 443 端口发给指定的 DoH 服务器(如 dns.google)。TLS 负责加密和完整性校验,中间人既看不到你问了什么域名,也改不了应答内容,等于把明信片换成了密封挂号信。

DoH 的代价是增加大约一个 RTT 的握手开销,所以通常配合连接复用和缓存缓解。Chrome、Firefox 等浏览器已逐步默认开启 DoH。需要特别注意:DoH 只保护“问路”这一段,网页本身的正文仍然要靠 HTTPS 加密,两者各司其职,不能互相替代。

把明信片换成挂号信后,途中没人能偷看你要去哪家面馆,也没人能偷偷改掉门牌号。不过走进面馆后的对话(网页内容)依然要靠店家自己的加密(HTTPS)来保护,这正是“DoH 不等于全站加密”的关键。

DNS 明文查询⏟明信片  →  TLS 封装  :443  HTTPS 请求⏟密封挂号信\underbrace{\text{DNS 明文查询}}_{\text{明信片}} \;\xrightarrow{\;\text{TLS 封装}\;:443}\; \underbrace{\text{HTTPS 请求}}_{\text{密封挂号信}}
DoH 的本质是把 DNS 查询从明文 UDP 报文,改造成走 443 端口的加密 HTTPS 请求,从而防窃听、防篡改。
  1. 左侧明文查询默认 DNS 查询不带加密,任何中间设备都能读取内容。
    DNS 查询报文⏟明文, 可被偷看\underbrace{\text{DNS 查询报文}}_{\text{明文, 可被偷看}}
  2. TLS 封装把查询作为 HTTPS 请求的载荷加密,目标端口从 53 换成 443。
    →  TLS 加密封装  :443\xrightarrow{\;\text{TLS 加密封装}\;:443}
  3. 右侧 HTTPS 请求接收方解密后取出 DNS 查询并正常应答,整个过程如同寄挂号信。
    HTTPS 请求⏟加密且防篡改\underbrace{\text{HTTPS 请求}}_{\text{加密且防篡改}}

完整解析流程:从输入网址到开门进店

最后把整条接力串一遍:在浏览器输入 www.example.com → 先翻浏览器缓存(未命中)→ 翻系统缓存与 HOSTS(未命中)→ 交给本地解析器 → 解析器查自身缓存(未命中)→ 迭代问根(得到顶级域地址)→ 问顶级域(得到权威地址)→ 问权威(得到 A 记录)→ 逐级缓存并回传浏览器。从敲回车到能建立 TCP 连接,通常只需几十毫秒。

这个耗时主要由“命中层数”和"RTT"决定。全链路未命中时,解析时间约等于用户到解析器的往返加上三次迭代往返的总和;命中任何一层缓存都会大幅缩短。抓包观察时,你会看到 DNS 请求 ID 一一配对的请求与应答,以及应答里附带的 TTL 值。

排障思路也顺着这条链走:先确认浏览器和系统缓存有没有残留旧条目,再用 nslookup 直接询问本地解析器确认是否有答案,最后指定权威服务器查询以剥离缓存干扰。哪一层没有响应,故障就定位在哪一段。

整个 DNS 就像一次环环相扣的问路接力:任何一环说错地址(劫持)、过期不更新(缓存)、或者干脆不接电话(服务器宕机),你都到不了阿婆面馆。理解了每一环的职责,以后再遇到“网站打不开”,你就能像老司机一样,迅速定位是哪一段路出了岔子。

T总≈T缓存命中+∑i=13RTTiT_{\text{总}} \approx T_{\text{缓存命中}} + \sum_{i=1}^{3}\text{RTT}_i
总解析耗时 ≈ 缓存命中的开销(命中时几乎为零)加上三段迭代各自的网络往返时延。优化方向就是提高命中率、缩短 RTT。
  1. T缓存命中任何一层缓存命中都会跳过后续全部查询,代价趋近于零。
    T缓存命中≈0  (命中时)T_{\text{缓存命中}} \approx 0\;\text{(命中时)}
  2. 求和符号三次迭代(根、顶级域、权威)各贡献一个网络往返 RTT。
    ∑i=13RTTi=RTT1+RTT2+RTT3\sum_{i=1}^{3}\text{RTT}_i = \text{RTT}_1+\text{RTT}_2+\text{RTT}_3
  3. 近似符号实际还包含报文解析、排队等次要开销,因此是近似关系。
    T总≈缓存的零成本+三段 RTTT_{\text{总}} \approx \text{缓存的零成本} + \text{三段 RTT}

AI 应用场景

CDN 智能调度:让解析器把用户领到最近的“面馆分店”

阶段:在线服务的接入层流量调度。

解决的问题:传统 DNS 把所有用户都解析到同一个机房,跨地域访问延迟高。AI 模型 API、视频分发等服务需要根据用户地理位置与边缘节点健康状况动态返回 IP,传统固定 A 记录做不到。

具体案例:某视频平台在华东、华南分别部署缓存机房,通过 GSLB(全局负载均衡)DNS 根据用户来源 IP 返回最近边缘节点。广州用户解析后得到华南节点地址,首屏加载耗时从 800ms 降到 120ms。整条链路依然是“查号台找门牌号”,只是查号台背后多了一张“就近推荐”的动态地图。

企业防劫持与 DoH 改造:给办公网络的“问路”加密封信

阶段:基础设施安全加固。

解决的问题:运营商级 DNS 劫持、缓存投毒会把用户悄悄引向仿冒网站,训练数据的下载、模型仓库的拉取都可能被篡改。DoH 将 DNS 查询加密,让中间人既看不到域名、也改不了响应。

具体案例:某金融企业通过浏览器策略批量下发 DoH 配置,把终端解析委托给可信递归服务,并在网关侧记录异常解析事件。上线三个月内拦截 30 余起仿冒域名请求,模型服务下载链路的完整性校验失败率降为零。

云原生服务发现:Pod 频繁漂移时的动态“门牌号”

阶段:AI 推理服务的微服务调用链。

解决的问题:GPU 集群中推理服务实例经常扩容缩容,Pod IP 随时变化,写死 IP 完全不可行。服务名通过 CoreDNS 动态解析到当前存活的 Pod IP,实现服务发现。

具体案例:某大促期间电商 AI 推荐服务扩容 1000 个 Pod,服务间相互调用全部依赖 DNS 动态解析完成。每次调用都像打查号台问“阿婆面馆现在哪个分店开门”,解析器根据健康检查结果返回存活实例,实现自动故障转移。

Python 演示

python86 行
# -*- coding: utf-8 -*-"""模拟 DNS 分级缓存与递归/迭代协作解析的全过程""" import time  class Cache:    """带 TTL 的简易缓存, 记录 (IP, 过期时刻)"""     def __init__(self, name):        self.name = name        self.store = {}     def get(self, domain):        record = self.store.get(domain)        if record is None:            return None        ip, expire = record        if expire < time.time():            del self.store[domain]            return None        return ip     def put(self, domain, ip, ttl=60):        self.store[domain] = (ip, time.time() + ttl)        print("  [%s] 登记: %s -> %s (TTL=%ds)" % (self.name, domain, ip, ttl))  AUTHORITY = {    "example.com": "93.184.216.34",    "mail.example.com": "192.0.2.10",}  def authoritative_answer(domain):    """权威服务器: 像总店登记处一样给出最终门牌号"""    print("  [权威服务器] 收到查询: %s" % domain)    return AUTHORITY.get(domain)  def iterative_lookup(domain, resolver_cache):    """本地解析器代表用户进行迭代查询: 根 -> 顶级域 -> 权威"""    ip = resolver_cache.get(domain)    if ip:        print("[解析器] 缓存命中, 直接返回 %s" % ip)        return ip    print("[解析器] 开始 3 跳迭代查询 (根/顶级域/权威)")    for hop in (1, 2, 3):        if hop < 3:            print("  [第 %d 跳] 服务器回答: 请去下一层查询" % hop)        else:            ip = authoritative_answer(domain)    if ip:        resolver_cache.put(domain, ip)    return ip  def full_resolve(domain):    print("=== 第 1 层: 浏览器缓存 ===")    browser_cache = Cache("浏览器")    ip = browser_cache.get(domain)    if ip:        return ip     print("=== 第 2 层: 操作系统缓存 ===")    os_cache = Cache("操作系统")    ip = os_cache.get(domain)    if ip:        return ip     print("=== 第 3 层: 本地解析器(接线员代办) ===")    resolver_cache = Cache("本地解析器")    ip = iterative_lookup(domain, resolver_cache)    if ip:        browser_cache.put(domain, ip, ttl=30)    return ip  if __name__ == "__main__":    target = "example.com"    print("用户输入: http://%s\n" % target)    result = full_resolve(target)    if result:        print("\n解析成功: %s -> %s" % (target, result))    else:        print("\n解析失败: 每一层都没有找到记录")
目的:从零模拟“浏览器缓存 → 系统缓存 → 本地解析器 → 根/顶级域/权威”的完整 DNS 解析链路,运行后直接输出每层缓存的命中状态与最终 IP。
  1. class Cache:用字典和过期时间模拟带 TTL 的缓存,get 时检查是否过期。
  2. if expire < time.time():只要过了保质期,立刻删除记录并返回 None,相当于撕掉过期便利贴。
  3. def iterative_lookup(...):本地解析器的核心逻辑:先查自身缓存,未命中则走 3 跳迭代。
  4. resolver_cache.put(domain, ip)权威返回 IP 后写入解析器缓存,下次直接念门牌号。
  5. if __name__ == "__main__":可执行入口,从第一层浏览器缓存开始逐层推进。

常见误区

误区DNS 就是把域名翻译成 IP 的一个简单步骤,浏览器拿到 IP 后这件事就结束了,不存在什么层级。

正解实际是一次多级缓存配合多轮查询的协作:浏览器缓存 → 系统缓存/HOSTS → 本地解析器 → 根 → 顶级域 → 权威。任何一层命中都会提前结束。就像找面馆不是只问一次路,而是先自己回忆、翻小抄,再让接线员层层转问,最后才拿到门牌号。

为何常见:用户平时感知不到背后的分工,只看到“敲回车 → 出页面”,自然误以为一步到位。

误区我在域名控制台把 A 记录改成新 IP 了,为什么过了好久别人访问的还是旧地址?DNS 系统是不是坏了?

正解TTL 决定了各级缓存保留旧答案的时长。旧记录可能被浏览器、操作系统、本地解析器层层缓存几分钟到几天,没到保质期谁都不会重新问路。这也是为什么正规迁移流程会“先调低 TTL 等旧缓存过期,再切换 IP”。

为何常见:不理解“记录下发”和“缓存失效”是两个时间点,把控制台的修改当成了全网同步广播。

误区用户发起查询后,根服务器、顶级域服务器都像接线员一样,一层层替我把答案查好再传回来。

正解只有本地解析器对客户端是递归代理;解析器向根、顶级域、权威发起的都是迭代查询,每次只获得“下一站去哪问”的指引。客户端像挂机等接线员回电话,而接线员内部是自己逐个部门去问,两者视角完全不同。

为何常见:把“为用户代办”和“服务器之间逐级追问”两个视角混为一谈,忽略了递归与迭代在查询链路上的角色分工。

误区只要开启 DNS over HTTPS,我访问什么网站、网页内容是什么,运营商和黑客全都看不到了。

正解DoH 只把“问路”这一段——DNS 查询报文——加密封装进 HTTPS。你访问哪些域名对中间人不可见了,但网页正文是否加密取决于 HTTP 还是 HTTPS。DoH 防止的是问路被偷听篡改,不代替网站自己做加密,两者各管一段。

为何常见:把“域名解析”和“网页传输”两条链路混为一谈,误以为一个加密措施能包打天下。

面试题

Q1. 浏览器访问 www.example.com 时,DNS 解析的第一步通常发生在哪里?

单选题 · easy

  • A.向根服务器发起查询
  • B.查询浏览器本地缓存← 正确答案
  • C.直接询问权威服务器
  • D.等待 DoH 服务器推送

答案:B。就像找阿婆面馆会先自己回忆,DNS 解析第一步永远是最靠近用户的浏览器缓存。只有缓存未命中才会逐级往上问。根服务器是链路中很靠后的环节,权威服务器更是最后一站。

Q2. DNS 协议默认使用哪个端口进行查询?

单选题 · easy

  • A.21
  • B.80
  • C.53← 正确答案
  • D.443

答案:C。传统 DNS 查询默认走 UDP/TCP 53 端口,像打查号台要用固定号码。21 是 FTP,80 是 HTTP,443 是 HTTPS——DoH 正是把 DNS 查询搬到了 443 端口。

Q3. 想把域名 example.com 直接映射到 IPv4 地址 93.184.216.34,应添加哪种记录?

单选题 · easy

  • A.CNAME
  • B.MX
  • C.NS
  • D.A← 正确答案

答案:D。A 记录就是“门牌号词条”,把域名直译为 IPv4。CNAME 是别名指向,MX 管邮件,NS 声明区域授权,它们都不负责最终的 IPv4 直译。

Q4. 在递归与迭代协作的解析体系中,谁承担“递归”角色?

单选题 · mid

  • A.根服务器
  • B.顶级域服务器
  • C.本地解析器← 正确答案
  • D.权威服务器

答案:C。本地解析器对用户全权代办,像接线员;它向上游(根/顶级/权威)发起的都是迭代查询。根、顶级域、权威服务器只是各自回答“下一站”,不做代理,浏览器也不是递归方。

Q5. TTL 在 DNS 缓存中的含义是什么?

单选题 · mid

  • A.域名总注册时长
  • B.记录在缓存中的最大存活时间← 正确答案
  • C.服务器开机时长
  • D.报文的传输耗时

答案:B。TTL 就是小抄的保质期。权威服务器下发记录时设定 TTL,缓存方在倒计时归零后必须重新查询,避免长期使用“面馆搬家前”的旧门牌号。

Q6. 一次典型的迭代查询中,根服务器通常返回什么?

单选题 · mid

  • A.目标域名的 IP 地址
  • B.下一级(顶级域)服务器的地址← 正确答案
  • C.完整的网页内容
  • D.客户端的加密密钥

答案:B。总机只做向导,不给门牌号。根服务器返回对应顶级域(如 com)的服务器地址,让解析器继续去问。目标 IP 只有权威服务器才会给出。

Q7. www.example.com 配置为 CNAME 指向 example.com,浏览器访问 www.example.com 时,解析器最终需要做什么?

单选题 · hard

  • A.只取 CNAME 记录即可
  • B.查 CNAME 后再查询 example.com 的 A 记录← 正确答案
  • C.只查 MX 记录
  • D.直接使用 53 端口的加密数据

答案:B。CNAME 是“招牌指向正门”,不是门牌号本身。解析器得到 CNAME 后,必须沿着目标名字继续查 A 记录才能拿到 IP。只取 CNAME 就好比看到“迁往对街”的告示却不过街。

Q8. DNS over HTTPS(DoH)带来的核心安全收益是什么?

单选题 · extreme

  • A.加密网页正文内容
  • B.让 DNS 查询继续走 UDP 53 端口
  • C.加密 DNS 查询并用完整性校验防止窃听与篡改← 正确答案
  • D.替代 HTTPS 证书体系

答案:C。DoH 把 DNS 查询装进 HTTPS 信封,既加密又防篡改,解决明文“明信片”被偷看、改址的问题。它不加密网页正文(那是 HTTPS 的职责),并且把通道从 53 搬到了 443。

Q9. 下列哪些是合法的 DNS 资源记录类型?

多选题 · mid

  • A.A← 正确答案
  • B.CNAME← 正确答案
  • C.MX← 正确答案
  • D.HTTP

答案:ABC。A/CNAME/MX 是查号台里三种常见词条,分别对应 IPv4 映射、别名指向、邮件投递。HTTP 是应用层协议,不是 DNS 记录类型。DNS 还常见 NS、AAAA、TXT 等记录。

Q10. 关于 DNS 解析全流程,下列说法正确的有?

多选题 · hard

  • A.客户端对本地解析器通常是递归查询← 正确答案
  • B.解析器对根/顶级域/权威服务器通常是迭代查询← 正确答案
  • C.TTL 过期后缓存记录依然会被优先使用
  • D.权威服务器是查询链路的最终答案来源← 正确答案

答案:ABD。递归 = 一个电话全权代办,迭代 = 逐级追问下一站,这正是 A、B 的写照;权威是终点站,对应 D。C 恰好相反——TTL 归零后缓存必须作废重查,否则面馆搬家了你还在用旧门牌。