简介:派小星DNS二级域名分发V2.0重构版是面向域名分发业务的整站型资源,适合需要集成腾讯云、阿里云、华为云、Cloudflare等多家DNS厂商,同时搭建会员分级、套餐折扣与实名风控体系的站长或服务商团队。相较旧版,代码全面重构,性能与扩展性更佳,后台UI和模块化插件市场也更利于日常运营。资源包共1371个文件,主体为681个js和400个css,负责前端交互与界面样式;67个php提供后台及接口逻辑,另有html页面、图标字体、音效等素材,压缩包整体仅16.93MB,部署轻量;其中sql文件可用于初始化数据库,so文件提供额外扩展支持。目前已有191人学习下载。通过这套完整系统,可快速上线二级域名分发平台,直观掌握多厂商DNS接入方式、会员等级折扣策略、多级套餐配置及身份三要素认证、违规内容拦截等安全风控逻辑,适合有域名分销或二次开发需求的技术团队参考。
1. 派小星DNS二级域名分发V2.0重构版在讲什么:把“手改解析记录”变成“租约式自动下发”
派小星DNS二级域名分发V2.0重构版,听起来像个小玩具,但它解决的问题一点都不小:给成百上千个终端自动分配client1.example.com这样的二级域名,并让解析记录在几分钟内全局生效。V1 老版本靠手工维护 zone 文件的方式,在客户端数量过百之后完全失控——改一条记录要 reload 一次区域,并发一高就漏写,缓存一乱客户端就开始报解析失败。V2.0 重构版的核心,是把“手改解析记录”变成“租约式自动下发”,每条二级域名都是一份带过期时间的租约,系统负责分配、续期、回收和下发。这篇文章适合要自己搭 DDNS、给内网穿透或物联网设备做域名分发的人,我会把数据模型、校验规则、幂等控制、部署参数和踩坑一条条讲清楚。
2. 二级域名分发 V2.0 的架构与数据模型:租约表、同步日志、后端适配层
2.1 为什么 V2.0 把“同步写记录”推倒改成“租约式状态机”
V1 时代的典型实现大家都见过:直接在 zone 文件里追加一条 A 记录,然后执行rndc reload。单机几十条记录时没问题,客户端一过百就麻烦:reload 期间新的写请求会排队,两条请求同时写同一个文件还会互相覆盖;更难受的是没有“这条记录到底写进去没有”的确认机制,reload 报错之后,你根本不知道 zone 文件里是半截记录还是旧记录。V2.0 重构版把整个模型推倒,改成租约式状态机。
每条二级域名记录不再是一个静态的“配置项”,而是一份带生命周期、归某个客户端所有的租约,状态只有三态:PENDING、ACTIVE、RECYCLED。客户端申请域名时先写数据库,状态为PENDING;后台任务把记录下发到 DNS 服务器并确认成功后,再把它更新为ACTIVE;客户端不再续期后,系统按过期时间把它翻成RECYCLED,等待延迟期结束再允许新客户端占用。这里最反直觉的一点是:V2.0 不追求“写 DNS 立刻成功”,它追求的是“数据库状态先落稳,DNS 下发异步确认”。因为 DNS 后端可能会宕机、超时、限流,同步写死在接口里,会连带拖垮分发接口本身。
2.2 分发表、租约表、同步日志表:三张表结构与唯一键设计
二级域名分发最重要的表就是租约表,我的做法下它有业务过期时间和物理占用结束时间两个字段。先看建表语句,这是我重构后一直沿用的结构:
-- 租约表:一条记录代表一个二级域名在某段时间内被某个客户端占用 CREATE TABLE subdomain_leases ( lease_id CHAR(32) PRIMARY KEY, client_id VARCHAR(64) NOT NULL, subdomain VARCHAR(63) NOT NULL, zone_name VARCHAR(255) NOT NULL DEFAULT 'example.com', record_type VARCHAR(8) NOT NULL DEFAULT 'A', target VARCHAR(255) NOT NULL, status ENUM('PENDING','ACTIVE','RECYCLED') NOT NULL DEFAULT 'PENDING', expire_at DATETIME NOT NULL, version INT NOT NULL DEFAULT 1, created_at DATETIME NOT NULL, lease_end DATETIME NOT NULL DEFAULT '9999-12-31 23:59:59', UNIQUE KEY uk_sub_end (subdomain, lease_end) ) ENGINE=InnoDB;这里有两个容易看漏的细节。第一个是expire_at和lease_end的区别:expire_at是业务上的过期时间,客户端必须在这个时间之前续期,否则视为放弃;lease_end是物理上的占用结束时间,活跃记录的lease_end统一默认是9999-12-31,回收时把它改写成当前时间戳。第二个是唯一键uk_sub_end,它把“活跃记录”和“历史记录”通过lease_end隔开:同一个subdomain只允许存在一条lease_end为 9999 的记录,所以并发申请时数据库会在这一层拒绝重复插入。
续期和回收都靠乐观锁控制并发。续期用UPDATE ... SET version=version+1, expire_at=? WHERE lease_id=? AND version=?,如果影响的记录数是 0,说明版本已经被别的请求改过,重新读取再决定是续期还是新建。回收任务则把status置为RECYCLED、把lease_end改为NOW(),不物理删除。这样做的原因很实际:DNS 缓存有滞后,真的把记录删了,老客户端还能在缓存里找到旧 IP,而新客户端却可能因为唯一键冲突写不进去。
另外两张表是同步日志表和适配层状态表。同步日志表记录每一次对 DNS 后端的调用动作、请求体、返回状态码和错误信息,出问题时就靠它还原现场。适配层状态表则记录每个 DNS 后端当前是否可用、最近一次调用延迟、失败次数,分发任务在调度时会优先挑状态健康的后端。
2.3 后端适配层:让 BIND、PowerDNS 和云解析 API 用同一套接口
V2.0 重构版并不绑定某个具体的 DNS 软件,它在分发服务和 DNS 服务器之间加了一层适配层。我一般会把适配层定义成三个操作,对应解析记录的增、删、清缓存:
# backend.py —— 所有 DNS 后端都要实现这三个动作 class DnsBackend(ABC): @abstractmethod def apply(self, zone: str, sub: str, typ: str, target: str, ttl: int) -> None: """创建或更新一条解析记录""" @abstractmethod def delete(self, zone: str, sub: str, typ: str) -> None: """删除一条解析记录""" @abstractmethod def purge(self, zone: str, sub: str) -> None: """主动清理该名称的解析缓存,尽量缩短旧记录的存活时间"""BIND 后端用nsupdate做增量更新,不整区 reload;PowerDNS 后端走它自带的 HTTP API;云解析后端则封装各家 SDK。接口收敛之后,分发主流程根本不关心底下是 BIND 还是云解析——它只调用apply和delete。换后端的成本也低,新接入一个 DNS 服务商只需要多写一个适配文件。
选型时我建议不要只看名气:BIND 最通用但缺少好用的 HTTP API,需要配nsupdate密钥;PowerDNS 自带 API 和缓存清理接口,最适合做二级域名分发;云解析 API 省运维,但很多家的最小 TTL 被限制在 600 秒,回收一条记录要等十分钟才能全局失效,这在分发场景下很憋屈。
3. 一次二级域名分发是怎么走通的:校验、冲突探测、写库、异步下发
3.1 子域名校验规则:长度、字符集、保留名单
分发接口收到的第一个请求参数字段通常是prefer_sub,也就是客户端想要的二级域名。这个字段绝不能直接拿去拼 DNS 记录,必须做严格校验,否则你能收获一堆格式奇怪的解析记录。校验规则不复杂但每条都来自实测踩坑:
- 字段长度必须在 4 到 63 个字符之间,超过 63 就是非法标签,DNS 协议根本不认;
- 只允许小写字母
a-z、数字0-9和连字符-,连字符不能出现在开头或结尾; - 统一转成小写,避免客户端传
MyNAS和mynas造成两条语义重复的记录; - 维护一份保留名单,
www、api、admin、mail、ns1这类系统名直接拒绝,避免和业务解析打架; - 全部校验在应用层做一遍还不够,数据库唯一键仍然要做兜底,应对极端并发。
我习惯的做法是:如果prefer_sub命中保留名单或已被占用,不直接拒绝整个请求,而是自动追加两位随机十六进制后缀,比如nas冲突后变成nas-3f。对客户端来说多一个后缀通常可以接受,而对系统来说它省去了一次失败重试的往返。
3.2 冲突探测和幂等控制
冲突探测有两层,第一层就是 2.2 节那个唯一索引,第二层是 DNS 实时探测。为什么需要第二层?因为数据库状态和 DNS 实际状态之间存在时间差:一条记录刚被回收,数据库已经标记RECYCLED,但 DNS 服务器上旧记录可能还没删干净。此时新客户端申请同一个子域,数据库层面没有冲突,可如果直接下发新记录,某些递归解析器可能仍然给你返回旧 IP。
实时探测的做法是分发的后台任务在写库之前,用dig直接查询目标 DNS 后端的权威解析,看看这个名称当前是否已有解析结果。若探测到旧记录,则先走适配层的delete,等确认删除后再执行apply。一个实用参数是probe_timeout=500ms,避免解析慢的后端拖垮整个分发流程。
幂等控制则围绕client_id展开。同一个client_id再次提交申请,系统不应该再创建一个新租约,而是找到它已有的活跃租约直接续期。实现上就是先查subdomain_leases表里有没有client_id相同且status='ACTIVE'的租约,有就把它的expire_at往后推。注意这里必须用唯一索引和事务兜底:没有索引时,同一个client_id并发请求还是能插进两条记录。
3.3 分发核心代码:一次申请从入库到下发
下面是分发接口的核心逻辑,我做了一点精简,但保留了状态流转和冲突处理的关键分支,可以直接抄回去改:
# dispatch_service.py —— 核心分发流程 from datetime import datetime, timedelta def issue_lease(client_id: str, prefer_sub: str, target: str, zone: str = "example.com"): # 1. 参数校验,防止把非法标签写进 DNS 记录 label = normalize_label(prefer_sub) # 只保留 a-z 0-9 - if not (4 <= len(label) <= 63): raise ValueError("二级域名标签需要在 4-63 个字符之间") # 2. 幂等:同一客户端已有活跃租约就直接续期,不重新分配 active = lease_repo.find_active_by_client(client_id) if active: lease_repo.renew(active.lease_id, ttl=TTL_NORMAL) return {"subdomain": active.subdomain, "status": "ACTIVE", "renewed": True} # 3. 事务内分配:依赖数据库唯一索引兜底,冲突时追加随机后缀 for _ in range(2): try: lease = lease_repo.insert( lease_id=uuid4().hex, client_id=client_id, subdomain=label, zone_name=zone, record_type="A", target=target, status="PENDING", expire_at=datetime.utcnow() + timedelta(hours=24), version=1, ) break except IntegrityError: # 并发申请撞车时,补一个随机后缀再试一次,而不是直接报错 label = f"{label}-{secrets.token_hex(1)}" else: raise ResourceBusy("候选二级域名全部冲突,请更换输入") # 4. 数据库落库成功,异步去调用 PowerDNS / 云解析 API dispatcher.enqueue("dns_apply", lease_id=lease.lease_id) return {"subdomain": lease.subdomain, "status": "PENDING"}注释里已经标注了四个关键分支:校验、幂等重续、冲突重试、异步下发。这里最容易被新手改成出问题的点,是把第 4 步的enqueue改成同步调用后端。如果你在分发接口里同步调用 PowerDNS API,一旦 PowerDNS 出现 2 秒超时,分发接口自己也跟着卡 2 秒,客户端那边看起来就像服务挂了。异步队列把“接收申请”和“下发记录”两个动作解耦,接口能保持毫秒级响应,下发失败还能在后台重试。
参数TTL_NORMAL我建议在初次分配时设置为 60 秒,等续期稳定后再提升到 300 秒。原因是新建记录的最大敌人是递归解析器缓存了旧的 NXDOMAIN,短 TTL 能让第一条记录尽快在递归层得到修正,续期说明解析已经稳定,再放宽 TTL 减少后续查询压力。
3.4 续期怎么和 DDNS 客户端配合
客户端侧的逻辑更简单,它要做的是定期调用续期接口,而不是每次都申请新域名。续期接口只需要识别client_id:
# 客户端定时任务:每 10 分钟续期一次,同一个 client_id 会被识别为续期 curl -s http://sdd.local:8080/v2/lease/renew \ -H "Authorization: Bearer ${SDD_TOKEN}" \ -d '{"client_id":"nas-01"}'服务端拿到请求后,先验证 token,再查这个client_id是否有活跃租约。如果剩余过期时间小于续期窗口(我建议renew_window = ttl * 2),就把expire_at向后延长;如果租约已经因为长期离线被回收,则走 3.3 节的新建流程重新分配一个域名。客户端拿到返回的subdomain后自行更新配置文件。
这里有一个常见的分歧点:客户端到底该在本地把域名缓存多久?我的建议是缓存时间不超过 TTL 的一半。很多客户端为了省流量,把 DNS 解析结果缓存好几个小时,结果服务端这边 TTL 只有 300 秒,回收后域名分配给新用户,老客户端还在用旧 IP 访问,问题就表现为“偶尔能通,偶尔不通”。与其相信客户端写的缓存逻辑,不如在服务端计算续期时把expire_at调得保守一点。
4. 部署与配置对比:自建 DNS 服务器还是接入云解析 API
4.1 小规模用云解析 API,规模化用 PowerDNS 自建
部署前先回答一个问题:二级域名分发的解析记录放在哪里。常见选择是云解析 API 和自建 DNS 服务器,没有哪个绝对好,取决于你手上有多少域和多少客户端。我的经验是:几百条记录以内,接云解析 API 最省事,不用维护 DNS 软件,域名解析的可用性由服务商负责;但云解析的限制在于 TTL 和 API 频率,很多家的最小 TTL 是 600 秒,而这套分发系统的回收与重分配周期可能远小于这个数。
当客户端规模上千,或者你需要秒级 TTL 来做快速回收和故障切换时,就应该转到自建。自建我推荐 PowerDNS 而不是 BIND,原因在于 PowerDNS 自带 HTTP API,支持动态创建、修改和删除解析记录,还有pdns_control purge这样的缓存清理命令。BIND 也不是不能用,但你需要为nsupdate单独配 TSIG 密钥,也没有便捷的缓存清理入口,运维成本高一个档次。
PowerDNS 开启 API 的最小配置长这样:
# /etc/powerdns/pdns.conf 关键配置 launch=gsqlite3 gsqlite3-database=/var/lib/powerdns/pdns.sqlite3 api=yes api-key=sdd-replace-this-key webserver-address=127.0.0.1 webserver-port=8081webserver-address必须绑内网地址,不要暴露到公网。API 的 key 不要硬编码在应用配置里,放到部署机的环境变量文件,权限 600。我这里用的是 SQLite 后端,数据量小启动简单;记录规模过万后,把launch改成gmysql接 MySQL,表结构用 PowerDNS 官方提供的 schema 初始化即可。
4.2 一套能直接用的参数表:TTL、续期窗口、重试上限
分发系统的运行质量其实就体现在几个参数上。直接给一张我梳理过的参数表,部署时照着填就不会出现“解析一直不变”或“记录反复丢失”的翻车情况:
| 参数 | 建议值 | 适用场景 | 备注 |
|---|---|---|---|
| initial_ttl | 60 秒 | 新分配记录 | 太短会给权威 DNS 造成多余查询压力,太长会导致回收后递归缓存残留 |
| stable_ttl | 300 秒 | 续期稳定后 | 客户端规模大时建议拉长到 600,减少权威服务器 QPS |
| renew_window | ttl × 2 | 全部 | 小于此时间的续期请求会被服务端认为是离线 |
| recycle_delay | 3600 秒 | 租约回收后 | 回收后等待至少 1 小时再允许新客户端占用 |
| queue_retry | 3 次 | 下发失败 | 超过重试次数进入 dead letter,人工介入 |
| api_rate_limit | 10 次/秒/IP | 公网接口 | 防止有人恶意批量申请二级域名 |
recycle_delay这个参数最容易被忽略,很多人认为租约过期了就能立刻把子域分给别人,实际做的时候会发现老客户端还能通过缓存解析到旧记录。给回收后的名称留一段冷却时间,能大幅降低新老用户解析到同一名称两种 IP 的概率。冷却期不够时,我见过一个真实的故障:凌晨回收的一个子域,上午分给新租户,结果新租户连续一周收到投诉说某个服务连不上,排查到最后是部分老客户端的递归缓存还指向旧 IP。
4.3 用 systemd 部署分发服务和 Docker 的边界
分发服务本身我建议直接跑在 systemd 下,不要为了容器化而容器化。原因很简单:这个服务需要稳定访问本地 DNS 后端的 API 端口,放容器里会增加一层网络转发,且容器的 DNS 配置还要单独操心。systemd 服务单元是这套服务最合适的部署形态:
# /etc/systemd/system/sdd-server.service [Unit] Description=SDD Dispatch Service After=network-online.target mariadb.service Wants=network-online.target [Service] Type=notify User=sdd ExecStart=/usr/local/bin/sdd-server --config /etc/sdd/sdd.yaml Restart=on-failure RestartSec=5 EnvironmentFile=/etc/sdd/env [Install] WantedBy=multi-user.target启动后观察日志的命令就不多说了,journalctl -u sdd-server -f是基本操作。如果分发服务里还要调用公网云解析 API,注意部署机的解析本身要稳定:不要在部署机上把/etc/resolv.conf指向自建的 PowerDNS,万一自建 DNS 挂了,你的分发服务连云解析 API 的域名都解析不了,形成连锁故障。
如果确实需要用容器跑某个子模块,记得容器内解析不要拿宿主机的方式照搬。Docker 守护进程的 DNS 在/etc/docker/daemon.json里配置,比如{"dns": ["223.5.5.5", "8.8.8.8"]};不要在容器里把dns设成127.0.0.1——那是容器自己,不是宿主机。Docker Desktop 这类带独立网络栈的环境,还需要在它的虚拟网络设置里单独指定 DNS,这个位置经常被人忽略,表现就是容器里能 ping 通 IP 但解析不了域名。
5. 重构版避坑清单:DNS 缓存残留、1012 事件与并发重复
5.1 下发成功但客户端解析仍然失败:TTL 和递归缓存残留
现象:分发接口返回成功,数据库状态也变成了ACTIVE,dig @自建DNS能查到记录,但客户端那边dig @公共递归要么返回旧 IP,要么直接 NXDOMAIN。
原因:公共递归解析器对域名有缓存,包括“这个域名不存在”的负面缓存。新建域名的解析在递归层刷新的速度取决于两条:一是上级 NS 记录的 TTL,二是解析器自身的缓存策略。有的递归服务器为了抗攻击,会强制放大负面缓存时间,导致明明权威服务器已有新记录,客户端却迟迟看不到。
解决:新分配的记录 TTL 压到 60 秒,并在分发成功后在适配层调用一次purge接口主动清缓存。PowerDNS 的pdns_control purge可以按名称清理,云解析没有 purge 接口时,只能靠短 TTL 等它自然过期。这也是为什么我会坚持:凡是给终端用户用的二级域名,初始 TTL 一律 60 秒,续期至少两次后再放宽。
5.2 Windows 事件日志里的“DNS 客户端错误 1012”
现象:客户端是 Windows 10/11,事件查看器里频繁出现“DNS 客户端事件 1012”,对应时间段内ping 域名失败或超时。
原因:1012 本质上不是权威 DNS 软件报错,而是 Windows 检测到自己本地的 DNS 服务器列表发生了变更。笔记本插拔网线、切换 Wi-Fi、开启移动热点时,系统把 DNS 服务器列表从“手动指定”重置为“DHCP 自动获取”,新拿到的递归解析可能是网关或运营商给的地址,它缓存了旧记录,于是客户端解析到的还是回收前的 IP。
解决:给这类 Windows 客户端固定 DNS 服务器,在命令行执行netsh interface ip set dns name="以太网" static 223.5.5.5,或者直接在网卡属性里写静态地址。服务端这边能做的辅助手段是:回收域名之后,强制等待recycle_delay再重分配,给 Windows 客户端的 DNS 变更留出足够的缓冲时间。不要试图靠客户端每次重启都重新解析来规避这个问题,Windows 的系统级 DNS 缓存有时比 TTL 更顽固。
5.3 Linux 改 DNS 重启后又被还原
现象:在 Ubuntu 22.04 上手工编辑了/etc/resolv.conf,当时生效,能解析域名;一旦重启网络服务或者机器重启,文件又变回原来的内容。
原因:现代 Linux 发行版里/etc/resolv.conf往往是指向systemd-resolved的软链接,NetworkManager 拿到新 IP 后会主动重写它。直接编辑这个文件等于改一个临时生成物,系统一刷新就还原。
解决:用系统提供的机制改。临时生效用resolvectl dns eth0 223.5.5.5;永久生效用 NetworkManager 的配置:
nmcli con mod eth0 ipv4.dns "223.5.5.5 8.8.8.8" nmcli con up eth0如果分发服务所在的机器必须依赖这份 DNS 配置,改完记得验证一下resolvectl status,确认生效的是预期地址而不是/etc/resolv.conf里那个软链接。这套动作做顺了之后,你会习惯一件事:排查解析问题前,先打开resolvectl status看当前实际生效的 DNS,别去盯着那个软链接文件。
5.4 并发请求分发出重复子域名
现象:两三个客户端同时申请同一个prefer_sub,数据库里出现了多条相同subdomain的ACTIVE记录,分发结果无法确定谁占用。
原因:应用层先查一次数据库判断“空闲”,再插入新记录,两个请求同时查都看到“空闲”,同时插入,如果表上没有正确处理唯一键约束,两条记录就都成功了。
解决:这里最靠谱的做法不是应用层加锁,而是数据库唯一键兜底。我在 2.2 节已经写了处理方式:活跃记录统一lease_end='9999-12-31 23:59:59',并在这个字段和subdomain上建联合唯一键。回收记录时只更新lease_end为当前时间,不物理删除,这样同一个子域名的历史记录不会影响唯一键判断。只要唯一键存在,并发插入最多成功一条,另一条抛出IntegrityError后走冲突重试逻辑。
5.5 子域名回收后老客户端还能解析旧地址
现象:一个二级域名因为租约过期被回收,随后分配给了新客户端,新客户端上报的解析地址是正确的,但部分老客户端仍然连连看旧 IP 访问,甚至因此改写了一部分业务数据。
原因:递归解析器和老客户端自身的缓存都没有过期,而你已经把这条记录分给了新用户。这相当于新老租户共用了一个名称,等于自己给自己制造映射混乱。
解决:回收动作必须设计成“带冷却的异步清理”,而不是“立刻分给下一个人”。具体顺序是:租约到点后先标记RECYCLED,此时不删 DNS 记录,只通过适配层把 TTL 改为 30 秒;等至少一个recycle_delay周期后,再执行delete彻底删掉解析记录;再等一个周期,才允许新客户端申请这个名称。回收期间的所有动作都要写进dns_sync_log,确保每一步失败都能追踪。说白了,域名回收是这整个系统里最需要“后悔药”的环节,宁可慢一点,也不要让两个租户在时间轴上重叠。
6. 最后一课:压测、拨测与回滚预案
6.1 模拟并发下发的压测方法
重构版上线前,我用下面的命令做过一次并发冲击,目标是验证 2.2 节的唯一键和 3.2 节的冲突重试逻辑是否真的可靠:
# 并发 50 个客户端同时申请同一个候选子域,每个客户端带独立 client_id seq 1 50 | xargs -P 20 -I{} curl -s -X POST http://127.0.0.1:8080/v2/lease \ -H 'Content-Type: application/json' \ -d "{\"client_id\":\"loadtest-{}\",\"prefer_sub\":\"shared\",\"target\":\"203.0.113.{}\"}" \ | grep -o '"subdomain":"[^"]*"' | sort | uniq -c正常结果应该是:50 个请求全部返回成功,但subdomain各不相同,因为撞车后自动追加了后缀;如果出现两条完全相同的subdomain且状态都是ACTIVE,说明唯一键没有生效,需要回查 2.2 节的建表语句,重点检查lease_end默认值设置。
6.2 用拨测脚本验证解析一致性
压测通过后,我会再起一个拨测脚本,同时用自建 DNS 和公网递归解析同一个测试域名,观察两边结果是否一致,这是判断下发是否真正生效的最直接手段:
# 拨测脚本:每 60 秒对比一次本地权威解析与公网递归解析 while true; do local_ip=$(dig +short nas01.example.com @127.0.0.1 | tail -n1) public_ip=$(dig +short nas01.example.com @223.5.5.5 | tail -n1) echo "$(date '+%F %T') local=$local_ip public=$public_ip" # local 为空说明自建 zone 没刷新;public 为空说明授权或 TTL 有问题 sleep 60 done这里有个容易被误判的地方:public_ip为空不一定是权威服务器的问题,也可能是公网递归在你刚建好的域名上还没有走过一轮完整查询,多等两个 TTL 周期再看。持续 15 分钟拨测一致,基本可以放心把这条记录分发给真正的客户端。
6.3 回滚需要提前准备的三个东西
回滚预案是我每次发布前必须确认的。回滚三步走:第一,旧版 zone 文件或后端数据库快照要保留至少 24 小时,自建 PowerDNS 就用它的 SQLite 文件拷贝,云解析就导出记录列表;第二,数据库迁移脚本必须支持双向执行,V2.0 往 V1 回退时,要能重建旧的 zone 文件并把客户端对应的旧解析记录恢复出来;第三,分发入口要有一道可独立关闭的闸门,出问题时先在网关层把分发接口拒绝掉,暂停新租约发放,再慢慢修数据,而不是直接回滚代码。
我现在养成的一个习惯是:每次发布后不看功能面板,先看 15 分钟的拨测日志和同步日志里的错误码。解析类系统的问题从来不会直接写在告警里,而是藏在“某个客户端过了 4 小时突然连不上”这类玄学反馈中,日志是最能还原现场的东西。这套租约式分发模型跑熟之后,最值钱的不是那几分代码,而是你心里那套“记录有生命周期、缓存有延迟、回滚要有闸门”的运维节奏。希望这篇笔记对你搭建自己的二级域名分发服务有帮助。
本文还有配套的精品资源,点击获取