二级域名分发系统实战:架构、DNS自动化与避坑指南

发布时间:2026/10/1 1:01:37
二级域名分发系统实战:架构、DNS自动化与避坑指南 简介这份资源是全新二级域名分发系统网站源码的终极最强版面向需要搭建二级域名分发平台的开发者与运维人员可用于研究多租户域名解析、流量调度与商业级防护的实现思路。压缩包共约2000个文件整体89.05MB以1228个PHP文件为核心业务代码辅以259个JSON配置、125张PNG界面素材、55个JS与35个CSS前端资源另有SQL建表脚本、模板文件与说明文档结构完整便于二次开发。系统基于PHP8.1与Swoole扩展构建支持高并发请求内置智能分发引擎、多租户主域名资源池、独立流量统计、CC防御与SQL注入防护并提供可视化面板、协程缓存加速、开放API接口及企业版与个人版多套前端主题。目前已有65人学习下载适合希望研究域名分发架构、接口对接与安全防护的读者参考需注意仅供研究学习使用。1. 二级域名分发系统到底在分发什么从一次踩坑说起去年帮一个做 SaaS 工具的朋友处理过一个挺尴尬的事故。他们主站app.example.com跑得好好的某天运营同事为了做一场活动手动在 DNS 面板加了一批promo-01.example.com到promo-50.example.com的解析记录结果其中三条 CNAME 指错了目标用户打开活动页直接跳到空白页客服电话被打爆。事后复盘问题根本不在 DNS 本身而在于「批量、可控、可回收地把二级域名分出去」这件事他们全程靠人肉操作。这就是二级域名分发系统要解决的问题。它本质是一套「域名资源池 分配规则 解析自动化」的组合用户在前台提交申请或触发某个动作系统按预设规则从池子里挑一个二级域名自动写入 DNS 解析同时把这条记录登记进数据库支持后续的查询、续期、回收和统计。常见落地场景包括多租户 SaaS 给每个客户分配独立子域、短链服务批量生成跳转域名、建站平台给用户开独立站点、活动页快速铺量。这套系统适合谁如果你手上有一个泛域名证书、一台能调 DNS API 的服务器、以及「域名分配」这个反复出现的需求那它值得做。反过来如果只是偶尔加两三条记录用面板点两下就够了上系统反而是过度设计。接下来我按「先讲清架构和选型再给可复现的落地步骤最后说坑」的顺序把这套东西拆开讲。2. 分发系统的核心架构与 DNS 选型为什么不能只写数据库很多人第一反应是「不就是往数据库插一条记录吗」。真跑起来会发现数据库里有一条user_a - sub001.example.com的记录和用户浏览器能打开sub001.example.com中间隔着 DNS 解析这一层。系统必须同时维护「业务侧的分配状态」和「DNS 侧的真实解析」两边任何一边掉链子用户看到的就是打不开。2.1 三层结构申请层、调度层、解析层我一般把系统拆成三层职责边界清晰出问题好定位。申请层负责接收请求和校验。用户提交想要的域名前缀、用途、有效期这一层做格式校验前缀只能是小写字母数字和连字符、敏感词过滤、频率限制。它不碰 DNS只产出「一条待分配的申请」。调度层是核心负责从域名池里挑一个可用前缀、检查是否冲突、生成分配记录、决定这条记录走哪条解析线路。它要处理并发——两个用户同时申请同一个前缀必须有锁或者唯一索引兜底。解析层负责真正调用 DNS 服务商的 API 写入记录并把 API 返回的 record_id 存回数据库方便后续修改和删除。这一层要处理 API 限流、超时重试、以及「数据库写成功但 DNS 写失败」这种半成功状态。三层之间用状态字段串起来比如申请记录有pending / allocated / resolved / failed几个状态任何一步失败都能从状态看出卡在哪。2.2 DNS 服务商怎么选API 能力比价格重要选 DNS 服务商时别只看解析速度和价格对分发系统来说API 的完整度和限流策略才是命门。你需要的能力至少包括创建记录、修改记录、删除记录、按名称查询记录、返回稳定的记录 ID。有些服务商的 API 只能整条覆盖不能单独改或者删除要靠「值匹配」而不是 ID批量操作时非常容易误删。下面这张表是我实际对比过的几个维度具体服务商名字按你自己的账号体系选这里只列判断标准判断维度合格线踩坑表现创建记录 API支持指定 name/type/value/ttl只能整域覆盖改一条动全量返回记录 ID创建后返回唯一 ID只能靠 namevalue 反查重名就乱删除方式按 ID 删除按值删除值相同会误删限流明确 QPS 且可申请提升突发批量直接 429无重试就丢记录TTL 下限支持 60s 或更低最低 600s回收后长时间残留提示TTL 别设太长。分发系统经常要回收和改指向TTL 设 600s 以上用户改了配置要等十分钟才生效排查时你会怀疑人生。2.3 泛域名与证书一次配好省掉后续麻烦二级域名分发几乎必然配泛域名解析和泛域名证书。泛解析*.example.com指向你的入口服务器这样新增的sub001.example.com不用单独加 A 记录也能被解析到。证书用*.example.com的通配符证书覆盖所有二级域名省去每个子域单独签发的麻烦。要注意泛解析和具体记录的关系如果你给sub001单独加了 CNAME它会优先于泛解析生效删除这条 CNAME 后又回落到泛解析。这个特性可以用来做「默认兜底页」——没被分配的二级域名全部落到一个提示页而不是报 NXDOMAIN。3. 从零跑通最小分发流程数据库、API 封装与分配逻辑这一章给能直接抄的代码。我用 Python 写数据库用 MySQLDNS 操作封装成一个类方便你换成任意服务商。整套逻辑跑通后你就能实现「提交前缀 → 自动分配 → 自动解析 → 可查询」的闭环。3.1 建表域名池和分配记录分开存先把数据结构定下来。域名池存「可用的前缀」分配记录存「谁在什么时候拿了哪个前缀、解析状态如何」。两张表分开池子可以批量导入记录可以追溯。-- 域名池预先生成好的可用前缀 CREATE TABLE domain_pool ( id BIGINT PRIMARY KEY AUTO_INCREMENT, prefix VARCHAR(63) NOT NULL UNIQUE COMMENT 二级域名前缀如 sub001, status TINYINT NOT NULL DEFAULT 0 COMMENT 0可用 1已分配 2禁用, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 分配记录谁拿了哪个前缀 CREATE TABLE allocation ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, prefix VARCHAR(63) NOT NULL, full_domain VARCHAR(255) NOT NULL COMMENT 完整域名 sub001.example.com, target VARCHAR(255) NOT NULL COMMENT 解析目标IP或CNAME, record_type VARCHAR(10) NOT NULL DEFAULT A, dns_record_id VARCHAR(64) DEFAULT NULL COMMENT DNS服务商返回的记录ID, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待解析 1已生效 2失败 3已回收, expire_at DATETIME DEFAULT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_prefix (prefix), KEY idx_user (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;domain_pool.prefix和allocation.prefix都加了唯一约束这是防并发重复分配的第一道闸。dns_record_id单独存一列删除和修改时靠它精确定位不靠值匹配。status字段把「待解析」和「已生效」分开DNS 调用失败时记录停在 0 或 2方便重试。3.2 封装 DNS API重试和幂等是重点DNS 调用是最容易翻车的地方。网络抖动、限流、超时都会让一次创建「看起来失败但其实成功了」重试时又创建一条重复记录。我的做法是创建前先按名称查一次存在就复用不存在才创建。import time import requests class DnsClient: def __init__(self, api_token, zone): self.api_token api_token self.zone zone # 如 example.com self.base https://api.your-dns-provider.com/v1 def _headers(self): return {Authorization: fBearer {self.api_token}} def find_record(self, name): 按名称查询记录返回 record_id 或 None resp requests.get( f{self.base}/zones/{self.zone}/records, params{name: name}, headersself._headers(), timeout10, ) resp.raise_for_status() items resp.json().get(records, []) return items[0][id] if items else None def upsert_record(self, name, rtype, value, ttl60): 存在则更新不存在则创建返回 record_id rid self.find_record(name) payload {name: name, type: rtype, value: value, ttl: ttl} for attempt in range(3): try: if rid: r requests.put( f{self.base}/zones/{self.zone}/records/{rid}, jsonpayload, headersself._headers(), timeout10, ) else: r requests.post( f{self.base}/zones/{self.zone}/records, jsonpayload, headersself._headers(), timeout10, ) r.raise_for_status() return r.json()[id] except requests.HTTPError as e: # 429 限流或 5xx 服务端错误退避重试 if r.status_code in (429, 500, 502, 503) and attempt 2: time.sleep(2 ** attempt) continue raise raise RuntimeError(dns upsert failed after retries)find_record先查后写保证幂等——重试不会产生重复记录。upsert_record里对 429 和 5xx 做指数退避重试最多三次。ttl60是默认值回收场景下短 TTL 能让变更快速生效。注意raise_for_status之后才判断状态码顺序别写反否则异常里拿不到r。3.3 分配逻辑用事务和行锁防并发分配的核心是「从池子里取一个可用前缀并标记为已分配」这一步必须原子。用SELECT ... FOR UPDATE锁住候选行再更新状态避免两个请求拿到同一个前缀。import pymysql def allocate(user_id, target, rtypeA, expire_days365): conn pymysql.connect(host127.0.0.1, userapp, password***, databasedomain, autocommitFalse) try: with conn.cursor() as cur: # 锁住一条可用前缀 cur.execute( SELECT prefix FROM domain_pool WHERE status0 ORDER BY id LIMIT 1 FOR UPDATE ) row cur.fetchone() if not row: raise RuntimeError(域名池已空) prefix row[0] # 标记池子已用 cur.execute( UPDATE domain_pool SET status1 WHERE prefix%s, (prefix,) ) full_domain f{prefix}.example.com # 写分配记录状态待解析 cur.execute( INSERT INTO allocation (user_id, prefix, full_domain, target, record_type, status, expire_at) VALUES (%s,%s,%s,%s,%s,0, DATE_ADD(NOW(), INTERVAL %s DAY)), (user_id, prefix, full_domain, target, rtype, expire_days), ) alloc_id cur.lastrowid conn.commit() except Exception: conn.rollback() raise finally: conn.close() # 事务外调用 DNS避免长事务占用锁 try: client DnsClient(api_token***, zoneexample.com) rid client.upsert_record(prefix, rtype, target) _update_dns_status(alloc_id, rid, status1) except Exception as e: _update_dns_status(alloc_id, None, status2) raise return full_domain关键点在「事务外调 DNS」。如果把 DNS 请求放在事务里一次超时就会长时间持有行锁池子被锁死。先提交数据库拿到alloc_id再调 DNS成功更新状态为 1失败更新为 2 并抛异常后续可以用定时任务扫描 status2 的记录重试。FOR UPDATE配合ORDER BY id LIMIT 1保证并发下每个请求拿到不同的前缀。3.4 回收与查询把状态机跑完整分配只是开始回收同样重要。用户到期或主动释放时要把 DNS 记录删掉、池子前缀标记回可用、分配记录置为已回收。def release(alloc_id): conn pymysql.connect(host127.0.0.1, userapp, password***, databasedomain, autocommitFalse) with conn.cursor() as cur: cur.execute( SELECT prefix, dns_record_id FROM allocation WHERE id%s AND status1, (alloc_id,), ) row cur.fetchone() if not row: raise RuntimeError(记录不存在或状态不允许回收) prefix, rid row # 先删 DNS再改库删失败就不动库保证一致 client DnsClient(api_token***, zoneexample.com) if rid: client.delete_record(rid) cur.execute(UPDATE allocation SET status3 WHERE id%s, (alloc_id,)) cur.execute(UPDATE domain_pool SET status0 WHERE prefix%s, (prefix,)) conn.commit() conn.close()顺序是「先删 DNS 再改库」。如果反过来库先标记回收、DNS 删除失败这个前缀就变成「池子里可用但线上还占着」的幽灵记录下次分配出去会冲突。删 DNS 失败时直接抛异常库不动状态保持 1人工介入或定时重试。4. 避坑与排查分发系统最容易翻车的五个地方这套系统逻辑不复杂但线上跑起来坑基本都集中在「数据库和 DNS 不一致」以及「并发和限流」上。下面五条是我和朋友实际踩过的按现象、原因、解决写清楚。4.1 现象用户拿到域名但打不开数据库显示已生效原因通常是 DNS 记录写成功了但泛解析没配或者记录类型和目标不匹配。比如用户要的是 CNAME 指向另一个域名系统却写了 A 记录指向服务器 IP浏览器解析到 IP 后 Host 不匹配直接 404 或证书报错。解决分配前校验record_type和target的匹配关系A 记录目标必须是合法 IPCNAME 目标必须是域名。同时确认泛解析*.example.com已生效用dig sub001.example.com和dig test-not-exist.example.com对比后者应该落到兜底页而不是 NXDOMAIN。4.2 现象批量分配时部分记录丢失日志里有 429原因就是 DNS 服务商限流。一次性分配几百个域名API 直接返回 429如果代码里没有重试这些请求就静默失败了但数据库状态可能已经写成「已生效」。解决所有 DNS 调用走统一的重试封装429 和 5xx 指数退避。批量场景下加一个令牌桶或信号量把并发压到服务商 QPS 以下。分配任务改成异步队列前台只返回「排队中」后台慢慢消费用户体验反而更好。4.3 现象两个用户拿到同一个二级域名原因是分配逻辑没有加锁两个请求同时SELECT到同一条可用前缀都以为自己是第一个。唯一索引能挡住数据库层面的重复插入但用户会看到「分配失败」而不是「换一个」。解决SELECT ... FOR UPDATE锁行或者用UPDATE domain_pool SET status1 WHERE prefix%s AND status0判断影响行数为 0 就重新取。唯一索引作为最后一道防线保留捕获重复键异常后自动重试下一个前缀。4.4 现象回收后域名还能访问等很久才失效原因是 TTL 设太长或者回收时只改了数据库没删 DNS 记录。前者是缓存问题后者是一致性问题。解决TTL 统一设 60s 或更低。回收逻辑严格按「先删 DNS 再改库」执行删 DNS 失败就抛异常不继续。加一个对账定时任务每天扫描allocation里 status1 但 DNS 查不到记录、或 status3 但 DNS 还有记录的条目自动修复并告警。4.5 现象泛域名证书不覆盖新子域浏览器报证书错误原因是证书是单域名证书或者通配符证书只覆盖一级*.example.com不覆盖a.b.example.com这种多级。解决确认证书是*.example.com形式的通配符证书且分发的前缀都是单级。如果业务需要多级子域得用*.b.example.com或更复杂的证书方案。上线前用openssl s_client -connect sub001.example.com:443 -servername sub001.example.com验证证书链。5. 进阶把分发系统做成可运营的基础设施跑通最小闭环后真正决定这套系统能不能长期用的是运营能力。我一般会加三个东西配额与审批、解析健康检查、以及数据看板。配额与审批解决「谁能分多少」。给每个用户设一个quota字段申请时先扣配额回收时返还。企业用户走审批流个人用户直接分配。这一步能挡住大量恶意刷域名前缀的请求。解析健康检查是定时任务对 status1 的记录做dig或 HTTP 探测连续失败三次就标记异常并通知。我踩过的坑是DNS 记录明明在但目标服务器挂了用户以为是我们系统的问题。有了健康检查能快速区分「解析问题」和「目标问题」。数据看板统计池子使用率、每日分配量、回收量、失败率。池子使用率超过 80% 就该补前缀了失败率突然升高通常是 DNS 服务商限流或 API 变更。最后说一个我自己的习惯所有 DNS 操作都记一条操作日志包含请求参数、响应、耗时、重试次数。分发系统出问题时日志是唯一的后悔药。我见过太多团队只记「成功/失败」真出问题连当时调的是哪个 API、返回了什么都不知道只能靠猜。这套东西不难难的是把「数据库状态」和「DNS 状态」当成一个整体来维护任何一边单独看都是黑匣子。把状态机、重试、对账这三件事做扎实它就能从一次性脚本变成能扛住运营的基础设施。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询