1987 年 11 月定稿的 RFC 1034,全名是 DOMAIN NAMES - CONCEPTS AND FACILITIES,一般译作《域名——概念与设施》。它和 RFC 1035 一起,给今天整个互联网订了寻址的规矩:你敲下 www.example.com 之后发生的一切——域名怎么分层、谁有最终解释权、一层层怎么问、答案在哪一级被缓存、为什么改完记录不能立刻生效——源头都在这两份文档里。
这份文档在今天读起来一点都不过时。你查一条 A 记录,递归解析器可能往返十几二十次,最终落到某台权威服务器;你看到报错说是“DNS 解析失败”,八成就是层次结构、缓存或委派某处出了问题。凡是跟网络、站点、云服务、邮件配置打交道的人,早晚都要和它定义的规则正面相遇。把 RFC 1034 的结构真正读一遍,比背一堆故障排查口诀有用得多。
1. 为什么 2024 年还要读一份 1987 年的文档
1.1 RFC 1034 到底定义了哪些关键概念
RFC 1034 是整个域名系统(DNS)的“思想宪法”。这份文档不写命令行,不写具体软件配置,它回答的是几个更根本的问题:域名空间应该长成什么样、由谁来管理、查询时谁说了算、结果如何缓存。文档主要内容可以分成四块。
第一是域名空间结构,把所有名字组织成一棵从根开始的树,每个节点都对应一个名字段,也就是标签。第二是资源记录框架,定义了 A、NS、CNAME、SOA、MX、TXT 等记录类型的用途和放置规则。第三是服务器体系,把服务器分成权威名称服务器和解析器两大类,并明确了递归与迭代两种查询模式。第四是维护机制,包括区域传送、SOA 序列号、TTL 缓存规则。
有个常见的理解误区需要先纠正:RFC 1034 讲的是“概念与设施”,RFC 1035 讲的是“实现与规范细节”,也就是报文格式、压缩算法、区文件写法。平时大家说“查 RFC 1035”更多是指看具体协议字段,而遇到“为什么域名体系要这样设计”“缓存边界在哪里”这类问题,答案几乎都在 RFC 1034 里。这两份文档是同一套设计的一体两面,只读一份容易一知半解。
1.2 谁应该认真读它
我的判断是:写网络程序的工程师、做云上基础设施的运维、处理过域名解析问题的后端开发,都应该至少完整过一遍。不是因为要背下来,而是为了建立正确的心理模型。
举个例子,很多人遇到“改了 DNS 记录半天不生效”,第一反应是“运营商缓存太慢”。但你如果知道 DNS 是拉模式而不是推模式,知道每个层级都有 TTL,知道负缓存也存在,就能算出最长延迟窗口,而不是干等。再比如,你把一个新的子域名交给第三方解析服务时,如果不懂“委派”(delegation),就可能出现 NS 记录配了、胶水记录没配,结果这个子域名三天两头间歇性失效。
这份文档还有一个价值:它帮你区分“标准不允许”和“软件实现不允许”。像裸域挂 CNAME 这种问题,RFC 层面就写死了;而另一些限制,比如来回串 CNAME 链的容忍度,不同实现差异很大。读懂原始规范,排障时就不会被具体软件的行为带偏。
2. 域名系统诞生的直接动机:中心化映射撑不住了
2.1 hosts.txt 的致命瓶颈
在 DNS 出现之前,主机名到 IP 的映射靠一个全网统一维护的文本文件,名字叫 hosts.txt。各组织把新机器的主机名和 IP 报给中心管理员,管理员维护全量清单,然后大家定期下载。
这个模式在小型网络里完全够用,但规模一起来就四处冒烟。文件体积越来越大,每台机器都要同步全量副本,光是分发带宽就让网络吃不消。更重要的是“一致性问题”:更新提交、审核、发布、分发这个链路有时间差,而任何一台机器在上一次副本过期前,看到的都可能是旧数据。
单一故障点更是要命。中心主机一旦出问题,全网的新机器都没法上线,老机器改个 IP 也会立刻造成全网找不到人。还有管理权限的冲突,组织之间的命名谁也管不住谁,叫 “server” 的主机一堆,撞车是家常便饭。RFC 1034 的引言部分把这些痛点讲得很直白:网络规模的增长让单一名字空间难以为继,必须寻找一种能在结构上分散的系统。
2.2 三个贯串至今的设计取舍
读 RFC 1034 给我最大的启发,不是它“做了什么”,而是它“拒绝做了什么”。
第一个取舍是拒绝集中。新系统采用树状命名空间,数据天然分布在无数个区域里,任何一台服务器都不需要保存全量信息。你只需要知道自己的区域内容,以及“往上走能找到谁”。这个设计直接解决了 hosts.txt 的体积和同步问题。
第二个取舍是拒绝“一刀切的正确”。DNS 允许缓存,允许短时间内返回旧数据,设计者从一开始就接受了“刚刚修改的解析结果不会立刻在全网生效”这个事实。它用 TTL 划定出错窗口,换取了巨大的性能收益。站在工程角度看,这是非常务实的妥协。
第三个取舍是拒绝“无脑的权威”。一个非权威服务器收到自己不负责的名字查询时,可以选择给你一条“引路信息”——告诉你去问哪台服务器,而不是永远说不知道。这种用“委派链”代替“全量复制”的思路,后来被许多分布式系统的设计反复借鉴。
这三个选择放到今天依然是互联网基础设施的精髓。我经常跟同行说,DNS 之所以能扛住全网规模的流量,不是因为哪台机器特别强,而是因为它的设计让任何单点故障都不至于拖垮全局。
3. 域名空间:一棵严格分层的树
3.1 节点、标签与根
RFC 1034 规定,域名空间是一棵树,根节点是一个“空标签”。从根往下,树上的每个节点都带一个标签,标签之间用点号连接。比如 www.example.com 实际代表三段标签:www、example、com,它在树上的位置是根 → com → example → www。
理解这棵树,许多日常现象就解释得通了。为什么很多命令行工具输出的域名末尾带一个点?比如dig里常见www.example.com.。那个点表示这是从根开始的“绝对路径”,也就是完全限定域名(FQDN)。没有末尾点的名字是相对名,它到底指谁,取决于查询者本地的搜索列表。线上系统里我强烈建议养成写完整 FQDN 的习惯,少一个点在配置解析时就是另一层语义。
树的另一个含义是“名字的归属关系”。example.com 是 com 的一个子域名,但当它被委派给一组独立权威服务器后,com 区域对 example.com 内部的数据就一概不负责了。这就像目录树里每个子目录可以被挂载到另一块磁盘:名字上是从属关系,数据管理上却是完全自治。域名空间的“树”不是用来看的,它是委派关系的结构基础。
3.2 长度、大小写与文本表示
RFC 1034 给名字定了几条硬性限制,今天照样生效。单个标签最长 63 字节,整个域名最长 255 字节。这里的 255 字节是把每段标签的长度字节和结尾的空标签都算进去的最终线编码长度,不是“字符个数”。
这个限制平时不起眼,但一碰上国际化域名(IDN)就很容易踩坑。IDN 在报文里用 UTF-8 编码,一个汉字占 3 字节,一个 emoji 可能占 4 字节。你填一个十几个汉字的子域名,视觉上不长,字节数已经逼近 255。有些注册系统会提示长度超限,指的就是字节数而非字数。
大小写方面,RFC 1034 规定 ASCII 范围内比较不区分大小写。配置里写WWW.Example.COM和www.example.com会被当作同一个名字查询。但注意,这只是比较规则;存储和显示时可以保留原始大小写,这就是为什么有些域名在证书里捆的是Example.COM而用户习惯写example.com,实际都能对上。至于 IDN 的 Unicode 等价长度折叠,那是后面标准补的内容,RFC 1034 管不到。
3.3 FQDN 与绝对名/相对名
做一个区分:完全没有后缀点的名字在解析上下文里是“相对名”,依赖本地配置的 search 列表来补全。绝大多数用户不会遇到,但跑脚本、写区文件、配服务时非常容易踩。比如在某台机器上解析server,解析器可能先试server,再试server.example.com,最后试server.example.org。如果代码里把这个行为当成必然,就可能在环境之间出现解析结果诡异不一致。
我见过一个案例:内部脚本里写的是不带点的短主机名,开发环境解析正常,测试环境因为 search 列表配置不同,解析到了完全另一台机器,排错排了大半天。从那以后,凡是在代码和配置里写主机名,一律写 FQDN,末尾点都不省略。这个习惯在树状命名空间里就是“把路径写绝对”,少歧义,少事故。
4. 分工明确的两种角色:权威服务器与解析器
4.1 权威服务器的“最终发言权”
权威名称服务器是对某个区域数据“说了算”的服务器。所谓区域,是域名空间树上的一个连续片段,由 NS 记录标出边界。权威服务器只对自己的区域负责,区域内的数据它给出权威答案,区域外的名字它可以正常查询,但不会“道听途说”直接返回。
这个“权威”概念是排查问题的关键。当你用dig @某台服务器 域名时,如果响应里 AA 标志位是 1,说明这台服务器就是该区域的权威,答案来自区域数据。如果 AA 是 0,说明你问的这台服务器只是转发或者递归查询到了结果,它给的是“缓存拷贝”,随时可能陈旧。
选权威服务器时,有个原则要记牢:不要只看“能不能解析”,要看“是不是这条链上被委派的那一台”。很多 DNS 故障的根因,是数据在一个环节改好了,但委派关系指向的服务器根本没更新,用户被一路引导到旧的那台。
4.2 解析器:把脏活外包出去
RFC 1034 把“替用户跑腿的查询方”统称为解析器(resolver)。解析器可以是一个库,也可以是一台独立服务,但它的核心职责是:把人类友好的域名转换成机器需要的地址信息,并且在转换过程中维护缓存。
解析器有两种常见形态。一个是驻留在操作系统或程序库里的“存根解析器”(stub resolver),它不做复杂迭代,直接把问题丢给本地配置的递归服务器;另一个是“递归解析器”,它负责从根开始向上向下追查到底。这种拆分非常聪明:普通设备只需要轻量客户端,重活由就近的公共递归服务承担。
实操中要注意:递归解析器每做一次查询,都可能把中间结果缓存下来,而且它自己不产生数据,只是“数据的搬运工”。所以当你怀疑“解析结果不对”时,第一件事不是骂 DNS,而是确认你是从权威答案看的,还是从缓存拷贝看的。dig默认不加@时用的就是本地递归,它给出的结果默认带着缓存属性。
4.3 委派与区域边界
委派是整个域名系统自洽运行的组织单元。父区域在 NS 记录里写明“某个子域交给某组服务器负责”,这就是一次委派。委派一旦发生,父子之间除了那条 NS 指针和必要的胶水信息,没有任何数据层面的强制同步。
真实的解析过程就是一连串委派的接力。查询 example.com 时,根域把 .com 委派给 .com 的权威服务器,.com 又把 example.com 委派给它的权威服务器,最终由后者的区域数据给出答案。每一跳都在“剥一层洋葱”,直到碰到有权威数据的区域。
给自己域名配解析服务时,最常犯的错就是把 NS 记录加在了子域区域内部,而不是父区域那里。比如想给 sub.example.com 独立解析,正确做法是在 example.com 区域加一条 NS 记录指向 sub 的服务器,而不是只在 sub 区域建文件。许多“配置没生效”其实是委派关系建错了层级。
5. 一次域名解析的完整旅程
5.1 从根出发:迭代查询拆解
假设你要访问 www.example.com,而且机器本地什么都没有缓存。整个流程是这样的:存根解析器把请求交给配置的递归解析器,递归解析器发现自己也不知道,于是从根区开始问。根服务器不会给出答案,它会说“.
com 的权威服务器是这些”,并附带服务器 IP。递归解析器再问 .com 权威服务器,得到“example.com 的权威服务器是这些”;再问 example.com 权威服务器,这次才拿到 www.example.com 的 A 记录。
这个过程叫迭代查询,也可以理解为“逐级问路”。另一种模式是递归查询,即客户端要求服务器全权处理到底。两者最大的区别在于“问路”发生在谁身上。公共递归服务帮你完成迭代,客户端只需要发一个请求,这是互联网上绝大多数场景。
| 查询步骤 | 询问对象 | 返回内容 | 性质 |
|---|---|---|---|
| 1 | 根服务器 | .com 的 NS 列表及地址 | 引路 |
| 2 | .com 权威服务器 | example.com 的 NS 列表及地址 | 引路 |
| 3 | example.com 权威服务器 | www.example.com 的 A 记录 | 权威答案 |
理解这个链路最大的价值在于定位故障。如果第 2 步就失败,问题大概率在注册局或域名的父区;如果第 3 步失败,才是你自己的服务器或区域文件问题。可惜很多排障的人第一步就切到自己的服务器上反复测,方向完全反了。
5.2 资源记录清单与挑选逻辑
RFC 1034 定义了资源记录(RR)的通用框架,今天常用的记录类型在它里面都能找到雏形。日常打交道最频繁的是这几类。
| 记录类型 | 全称 | 典型用途 |
|---|---|---|
| A | Address | 域名指向 IPv4 地址 |
| AAAA | IPv6 Address | 域名指向 IPv6 地址 |
| NS | Name Server | 标记区域的权威服务器 |
| CNAME | Canonical Name | 别名指向另一个域名 |
| SOA | Start of Authority | 区域起始与版本信息 |
| MX | Mail Exchanger | 邮件交换服务器优先级 |
| TXT | Text | 任意文本,常用于校验与策略 |
| PTR | Pointer | 反向解析,IP 指向域名 |
重点说说 CNAME 的硬限制。RFC 1034 明确写了:一个拥有 CNAME 记录的名字,不允许再拥有任何其他类型的数据,包括 A、AAAA、MX、甚至 NS、SOA。从这规则能直接推导出两件事:其一,裸域(根域名)正常情况下不能挂 CNAME,因为裸域必然有 SOA 和 NS;其二,CNAME 不应链式指向另一个 CNAME,虽然不少实现能容忍,但标准并不推荐。
域名的“文本表示”在 RFC 1034 里也有规定,就是点号分隔、从左到右,从最具体的标签写到最上层的标签。这套表示法看着简单,实际上影响深远:所有解析工具、区文件、各类配置都遵循同一套文本格式,才让 DNS 生态不至于各自为政。
5.3 胶水记录:先有鸡还是先有蛋
委派场景里有一个经典悖论。假设你把 example.com 的权威服务器设成 ns1.example.com,那么想解析 example.com 时,必须先去问 example.com 的权威服务器;但你要问这台服务器,又需要知道 ns1.example.com 的 IP,而 ns1.example.com 恰好就在 example.com 区域里。这不成了先有鸡还是先有蛋?
RFC 1034 的解法是“胶水记录”(glue)。父区域 .com 在委派 example.com 时,除了给出 NS 记录,还把 ns1.example.com 对应的 A 记录一并放在附加区。递归解析器拿到 NS 指针的同时就拿到了地址,直接就能连过去。胶水在正常情况下并不参与 authoritative 解析,它只是帮助“起步”的辅助信息。
自己配置域名时,如果 NS 指向的服务器名字就在当前域名之下,一定要在注册商或域名管理平台额外确认“胶水已配置”。很多服务商提供自动注册胶水,但自建权威服务器或小众 DNS 平台时,经常漏这一步,结果就是外部递归解析器连目标服务器都找不到,域名全线下不了线。
5.4 从响应结构反推问题
一次 DNS 响应里,Answer、Authority、Additional 三个区各有用处。Answer 区是答案本身;Authority 区通常放 SOA(权威答案时)或 NS(委派引路时);Additional 区放胶水或额外地址。字段去向本身就是线索。
如果 Authority 区出现的是 NS 记录,说明你得到的只是“去哪儿问”的指引,不是最终答案;如果出现的是 SOA,说明这台服务器对该名字有权威,而且答案就在 Answer 区。排障时可以据此快速区分:到底是缓存里的老答案、中转的委派信息,还是真的权威结果。
还有一类情况让很多人困惑:域名存在,但没有你想要的记录类型。比如域名有 A 记录,你查 TXT,响应 RCODE 是 NOERROR,但 Answer 区为空。这不是故障,而是“名字存在、类型缺失”。如果响应 RCODE 是 NXDOMAIN,才是真的“这个名字不存在”。这两者在负缓存里的处理逻辑也不同,值得记清楚。
6. 缓存、TTL 与“传播”的真相
6.1 缓存是 DNS 的命根子
递归解析器给客户端返回结果的同时,会把沿途拿到的东西缓存下来:A 记录、NS 记录、甚至“查询失败”的状态。缓存把一次可能要触发几十条网络请求的解析压缩成单次本地命中,同时大幅减少对根服务器和权威服务器的冲击。
RFC 1034 的设计目标里,缓存不是可选项,而是整个体系能撑住全网规模的前提。讽刺的是,缓存也成了大多数“DNS 故障”的替罪羊。实际上绝大多数解析异常不是“DNS 坏了”,而是“数据老了”“TTL 还没过期”“某台递归器把负结果缓存太久了”。
6.2 TTL 与负缓存
每个资源记录都有 TTL,表示“这条记录能在缓存里存活的最长时间”。RFC 1034 的语义是:这是一个上限,缓存可以提前让它过期,但不应当超过它。TTL 为 0 表示“别缓存,每次向上游问”。这最常用于动态数据或故障切换场景,但代价是权威服务器压力陡增。
还有一个概念经常被忽略:负缓存。RFC 1034 里,SOA 记录最末尾的 minimum 字段被用来作为“查不到”结果的默认缓存时间。后来 RFC 2308 正式把它定义为负缓存 TTL,几乎所有递归实现都遵守。换句话说,不仅“成功的答案”会过期,“没找到”的失败结果也会过期。
把这个逻辑捋清楚,就能理解一个常见现象:你新加了一条子域名解析,但过一个多小时仍然大面积解析不出来。原因很可能是各地公共递归早前已经缓存过“该子域名不存在”的负结果,TTL 没到,它们根本不会再次向上游确认。这种坑比正向缓存过期更隐蔽,因为改记录时很少有人会想起去清理负缓存。
6.3 改域名前先调 TTL 的实战顺序
“传播时间”其实是个伪概念。DNS 没有广播机制,没有全局刷新。改完记录后,全世界缓存过期是各自独立、随时间涟漪式发生的。所以真正可控的做法是:在变更前先缩短 TTL。
我总结的操作顺序是这样的。第一步,提前至少一到两个 TTL 周期,把要变更记录的 TTL 从几小时改成 300 秒。第二步,等旧 TTL 周期走完,确认线上各递归器拿到的都是新 TTL。第三步,才做真正的记录变更。第四步,观察一段时间后,再把 TTL 调回长周期。
这里最怕的是“为了赶时间”省掉第一步。我见过有人把线上记录的 TTL 改成 60 秒,然后立刻换 IP,结果其他厂商 CDN 的探测节点还在用旧缓存,把流量打向旧地址,持续了整整几个小时。DNS 的 TTL 就是你发布变更的“缓冲期”,不给自己留缓冲,就得给故障留窗口。
7. 区域维护与主辅同步
7.1 SOA 记录里的六个字段
每个区域都必须有且只有一条 SOA 记录,它相当于区域的“户口本”和版本号。RFC 1034 定义了 SOA 的字段结构,从主服务器名字到各种时间参数,再到负责人邮箱。六个字段各有各的用途。
| 字段 | 含义 | 实战注意 |
|---|---|---|
| MNAME | 主权威服务器名字 | 辅服务器从这里拉数据 |
| RNAME | 区域负责人的邮箱 | 反写格式,如 hostmaster.example.com |
| Serial | 区域版本号 | 必须递增,否则辅服务器不更新 |
| Refresh | 辅服务器刷新间隔 | 主服务器不可达时的重试节奏 |
| Retry | 重试间隔 | 刷新失败后隔多久重试 |
| Expire | 过期时间 | 辅服务器认为数据失效的期限 |
| Minimum | 负缓存 TTL | 后来被 RFC 2308 正式定义为负缓存时长 |
这么多字段里,序列号是最容易被忽略又最容易出事的。主流做法是 YYYYMMDDnn 格式,比如 2024110501,同一天改第二次就末尾加一。重要的是:改了区域数据却不升序列号,等于什么都没改——辅助服务器拿不到任何同步信号,用户继续被旧答案喂着。
7.2 区域传送:AXFR 与 IXFR
辅助权威服务器通过区域传送同步区域副本。RFC 1034 描述的是全量传送 AXFR:辅服务器向主服务器请求整个区域数据,然后与本地做比对替换。后来 RFC 1995 加入了增量传送 IXFR,只传输 serial 变化的记录片段。
理解传送机制,便于回答“为什么我的主服务器改了,辅助服务器一直不给力”。刷新流程是这样的:辅服务器每隔 refresh 秒检查一次主服务器的 SOA;若 serial 变大,才触发传送。如果 serial 不变,即使你改了数据,它也认为“世界没变化”。很多“主从不一致”的根源不是网络问题,就是 serial 没有递增。
现在不少托管平台还支持 NOTIFY 机制,让主服务器在数据变更时主动通知辅服务器,缩短等待周期。但 NOTIFY 只是一个加速器,底层判断还是 serial。运维习惯上,我给所有自建 DNS 环境定了两条铁律:改区域前先看 serial,改区域后一定升 serial;辅服务器日志里出现任何“serial mismatch”,先怀疑版本号而不是网络。
8. 真实运维中的高发坑与排查建议
8.1 裸域 CNAME 的困局
网上经常看到有人问“为什么我不能把 example.com 直接 CNAME 到托管平台”。答案就在 5.2 节说的那条硬限制:裸域同时是 SOA 和 NS 的属主,CNAME 不能与别的记录共存。所以裸域在标准 DNS 里不能直接充当别名。
解决路径有三条。一是把裸域解析到一个固定 IP,用 Web 服务器做 301 跳转到带 www 的域名;二是用支持“flat 别名”的托管服务,让它在 DNS 层面动态地给裸域生成 A 记录,比如 ALIAS、ANAME 这类实现;三是把主站就放在裸域,www 用 CNAME 指过去。选择哪条取决于业务入口,而不是技术偏好。
这个规则还衍生出一个搜索排名相关的习惯问题:很多人只设置了 www,忘了处理裸域,结果用户访问 example.com 得不到任何解析。从消防角度说,裸域和 www 都应该至少有一条可解析的记录,再决定谁是主谁是辅。
8.2 内外网分裂的同步陷阱
RFC 1034 并没有规定“一个名字全局只能有一个答案”。只要两套权威服务器各自维护不同内容的区域副本,同一个域名在不同视角下完全可以给出不同地址。这就是“分裂 DNS”“双视域”的常见做法:内网访问内网 IP,外网访问公网 IP。
听起来美好,坑却不少。最常见的是两套区域文件的 serial 更新节奏不同步,改了内网忘了外网,或者反过来。其次是内外网权威服务器各自委派的子域结构不一致,导致内网解析正常、外网解析路径直接断掉。还有一种隐蔽情况:内网客户端同时配置了公网递归和自己内网递归,查询结果时而对时而错。
处理这类问题,我给的最实用建议是:把双视域当作两套独立系统来管理,版本号各自独立但要有对照表;排查时先明确“从哪个视角问的”,避免拿着内网结果和外网配置对比。任何一次变更,发版清单里都要写上“内外网各改一遍、各验证一遍”。
8.3 排障工具链与思路
最后分享一套我自己长期使用的 DNS 排障思路,用 dig 就能完成绝大多数定位。
# 查基础解析,走本地默认递归 dig www.example.com A # 指定某台服务器查询,绕开本地缓存 dig @192.0.2.1 www.example.com A # 从根开始完整追踪解析路径 dig +trace www.example.com A # 查当前域名委派到的 NS 列表,确认委派关系 dig NS example.com # 查区域版本号,确认数据是否更新 dig SOA example.com判断响应区内容的逻辑前面已经说过:Answer 区是答案,Authority 区是 SOA 就是权威结果、是 NS 就是引路信息,Additional 区是胶水地址。再配合 RCODE 判断:NOERROR 但无答案,通常是“名字在、类型不在”;NXDOMAIN 是“名字真不存在”;SERVFAIL 多半是权威服务器或链路问题;REFUSED 是服务器明确拒绝查询。
实战排障步骤我习惯这样走:先用+trace看整条链路有没有在某一段断裂;再直接指定权威服务器验证区域数据本身;最后检查递归服务器给的答案与权威答案是否一致,不一致就查缓存和 TTL。这套顺序能覆盖绝大多数“时通时不通”的诡异问题。我自己把 RFC 1034 当作排查 DNS 问题的“备用地图”,每当现象复杂到工具都解释不了时,回到这份文档重新推演一遍,往往比瞎试参数有用得多。技术标准会过时,但底层的概念和思维结构,比任何具体命令都活得久。