新用户新用户即享 DNS 与 CDN 免费套餐。立即注册

我们的 DNS 如何为你的域名挑选名称服务器

每个托管在 Yeap DNS 的域名都会分到一对名称服务器。这对 NS 要稳定、要分散、还必须真的能应答。本文介绍我们基于稳定哈希的分配方式,以及为什么"状态是意图,心跳才是事实"。

当你在 Yeap DNS 添加一个域名时,控制台会告诉你两台名称服务器(NS),让你去注册商那里把域名委派过来。这一步看似简单,背后其实有几个必须同时满足的要求。这篇文章讲讲我们是怎么挑选这两台 NS 的。

三个要求

  1. 稳定。NS 会被写进注册商和顶级域的委派记录里。如果同一个域名今天分到 A 和 B、明天变成 C 和 D,用户就得反复修改注册商设置,期间还可能解析失败。所以对同一个域名,分配结果必须是确定的。
  2. 分散。如果所有域名都分到同一对 NS,负载会集中在少数节点上,单个名称服务器出问题时影响面也会很大。不同的域名应该尽量分散到不同的组合上。
  3. 真的能应答。分配给你的 NS 背后必须有正在运行的节点。把域名委派给一台没有节点承载的名称服务器,等于让解析请求打到空处。

池模型

我们的模型和很多托管 DNS 服务类似,分为三层:

  • 池:一组名称服务器的集合。不同的套餐指向不同的池,免费套餐和付费套餐使用相互独立的池。
  • 名称服务器:池中的每一项对应一个 NS 主机名,也就是你最终在注册商处填写的那个名字。
  • 节点:真正运行权威 DNS 服务的机器。一个名称服务器可以由多个节点承载,它的胶水记录(glue)会指向这些节点的地址。

为一个域名分配 NS,就是从它所属套餐对应的池中,选出两个名称服务器。

稳定随机:用域名做种子的哈希排序

要同时做到"稳定"和"分散",我们用的是一种很经典的方法,本质上是最高随机权重哈希(Rendezvous Hashing,HRW):

  1. 对池中的每一个名称服务器,计算 hash(域名 + ":" + 名称服务器ID);
  2. 按哈希值排序(哈希相同时按 ID 排序,保证确定性);
  3. 取排在最前面的两个。
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 免费套餐。

免费注册