我们的 DNS 如何为你的域名挑选名称服务器
每个托管在 Yeap DNS 的域名都会分到一对名称服务器。这对 NS 要稳定、要分散、还必须真的能应答。本文介绍我们基于稳定哈希的分配方式,以及为什么"状态是意图,心跳才是事实"。
当你在 Yeap DNS 添加一个域名时,控制台会告诉你两台名称服务器(NS),让你去注册商那里把域名委派过来。这一步看似简单,背后其实有几个必须同时满足的要求。这篇文章讲讲我们是怎么挑选这两台 NS 的。
三个要求
- 稳定。NS 会被写进注册商和顶级域的委派记录里。如果同一个域名今天分到 A 和 B、明天变成 C 和 D,用户就得反复修改注册商设置,期间还可能解析失败。所以对同一个域名,分配结果必须是确定的。
- 分散。如果所有域名都分到同一对 NS,负载会集中在少数节点上,单个名称服务器出问题时影响面也会很大。不同的域名应该尽量分散到不同的组合上。
- 真的能应答。分配给你的 NS 背后必须有正在运行的节点。把域名委派给一台没有节点承载的名称服务器,等于让解析请求打到空处。
池模型
我们的模型和很多托管 DNS 服务类似,分为三层:
- 池:一组名称服务器的集合。不同的套餐指向不同的池,免费套餐和付费套餐使用相互独立的池。
- 名称服务器:池中的每一项对应一个 NS 主机名,也就是你最终在注册商处填写的那个名字。
- 节点:真正运行权威 DNS 服务的机器。一个名称服务器可以由多个节点承载,它的胶水记录(glue)会指向这些节点的地址。
为一个域名分配 NS,就是从它所属套餐对应的池中,选出两个名称服务器。
稳定随机:用域名做种子的哈希排序
要同时做到"稳定"和"分散",我们用的是一种很经典的方法,本质上是最高随机权重哈希(Rendezvous Hashing,HRW):
- 对池中的每一个名称服务器,计算
hash(域名 + ":" + 名称服务器ID); - 按哈希值排序(哈希相同时按 ID 排序,保证确定性);
- 取排在最前面的两个。
function pickNameservers(pool, zoneName, count = 2):
if len(pool) <= count:
return pool
sort pool by (crc32(zoneName + ":" + ns.id), ns.id)
return first count items of pool
这个方法有几个很好的性质:
- 确定性:输入只有域名和池的内容,同一个域名无论计算多少次,结果都一样,不依赖任何随机数状态或者分配时刻的负载。
- 均匀分散:不同的域名会得到看起来随机的不同排序,整体上均匀落在池中各个 NS 组合上。
- 扩容影响小:向池中加入一个新的名称服务器,只会改变那些"新成员恰好排进前两名"的域名的计算结果,其他域名的结果不受影响。这是 HRW 相对于简单取模哈希的主要优势。
需要说明的是,分配结果在域名创建时就会被保存下来。之后池的变化不会自动改动已有域名的 NS,委派关系不会因为我们的扩容而突然变化。哈希的确定性更多体现在:同一个域名在任何路径下创建(控制台、API、购买套餐后自动开通),都会走同一个分配器,得到同一个结果。
当没有域名可以作为种子时(极少数内部路径),分配器会退化为"当前承载域名最少的优先",作为一种确定性的负载均衡。
状态是意图,心跳才是事实
排序之前还有一步更重要的事:决定哪些名称服务器可以进入候选池。我们称之为"可承载",必须同时满足:
- 名称服务器本身处于启用状态;
- 有合法的 NS 主机名(不为空,不是测试用的占位名);
- 至少绑定了一个节点,并且这个节点的状态是启用,同时在最近 15 分钟内有过心跳。
最后一条值得展开。数据库里节点的"状态"字段是管理员设置的:它表达的是"我们希望这个节点在服务"。但希望不等于现实,一台机器可能被标记为启用,却因为重装、网络变更或者配置失效而早已不再应答。如果分配器只看状态字段,就可能把新域名委派到一个实际上并不存在的服务上。
所以我们的原则是:状态字段是意图,心跳时间才是事实。节点每次拉取配置时都会刷新自己的最后在线时间;超过一定时间没有拉取的节点会被自动标记为离线。分配器只相信事实。
节点如何拿到你的区域数据
分配完成后,区域数据需要出现在对应的节点上。我们的权威 DNS 数据面是在 CoreDNS 基础上定制的,采用拉取模式:
- 每个区域有一个配置版本号,它同时就是该区域 SOA 记录的序列号。你修改任何一条记录,版本号都会递增。
- 节点定期带着认证信息向控制面查询版本。发现版本变化后,拉取新的区域数据,写成标准的 zone 文件并重新加载。
- 每个节点只会收到分配给它所在名称服务器的区域,而不是全量数据。
- 当一个区域被停用或删除时,控制面会在一段时间内持续向节点下发删除信号,节点据此移除本地的 zone 文件,不再应答这个域名。这样已删除的域名不会在某些节点上残留。
由于 SOA 序列号就是配置版本,你可以直接用 dig 检查某个名称服务器是否已经同步到最新版本:
dig +short SOA example.com @你的NS主机名
不同名称服务器返回的序列号一致,说明它们都已经拿到了最新数据。
一个关于"基础设施区域"的教训
所有 NS 主机名本身也需要被解析,它们的胶水记录和地址记录存放在一个专门的基础设施区域里。这个区域和普通用户的区域一样,由同一套系统管理。这意味着它的状态必须始终保持启用:如果它被误停用,删除信号会让节点移除它,进而影响所有依赖这些 NS 的域名。我们在上线删除信号之前的端到端测试中专门检查了这一点,并把这类区域标记为必须常驻。
这个教训适用于任何"自举"的系统:当你的平台用自己来托管自己的关键依赖时,这些依赖需要额外的保护。
你需要做什么
作为用户,你只需要在添加域名后,把控制台显示的两台 NS 填到注册商处。域名详情页会显示分配给该域名的名称服务器,以及每台名称服务器当前活跃的节点数量;控制台的 NS 检查功能会告诉你委派是否已经生效。剩下的事情交给分配器和同步机制。
在 Yeap Cloud 上构建
新用户即享 DNS 与 CDN 免费套餐。
继续阅读
TLS 会话恢复与 HTTP/3:边缘上的握手优化
会话恢复能让回访用户省掉一次完整的 TLS 握手。我们讲讲票据密钥为什么必须跨重启持久化、如何安全地轮换,以及边缘上的 HTTP/3 是怎样复用同一套业务逻辑的。
10 分钟把网站接入 Yeap CDN 并开启免费 HTTPS
一步一步把现有网站放到 Yeap CDN 后面:添加域名、配置源站、用 CNAME 或 NS 接入、申请免费 Let's Encrypt 证书、选择 SSL 模式并强制 HTTPS,最后用 curl 验证。
缓存清除实用指南:从缓存键到变体级精确清除
清除了缓存却还是命中?大多数时候问题出在缓存键上。本文解释 Yeap CDN 的缓存键如何构成、查询参数的几种模式,以及带与不带查询参数的 URL 清除分别会清掉什么。