Skip to content

[L2] DNS 解析流程与 CDN 接入原理

一句话结论

DNS 递归委托解析,TTL 控缓存,CNAME 转交 CDN 就近调度。

体系讲解

1. DNS 解析的层级结构

浏览器发起请求时,域名解析按以下顺序命中即止:

本地 hosts 文件 → 操作系统 DNS 缓存 → 本地 Resolver(ISP 或 8.8.8.8)
    → 根域名服务器(.)→ TLD 服务器(.com)→ 权威域名服务器

2. 递归查询 vs 迭代查询

两者并非互斥,而是发生在不同阶段:

阶段类型含义
客户端 → 本地 Resolver递归查询客户端委托 Resolver 全权解析,只等最终结果
本地 Resolver → 根/TLD/权威迭代查询Resolver 依次询问,每次只获得"下一步去哪问"的引用

3. TTL 与缓存控制

每条 DNS 记录都携带 TTL(秒),各级缓存按此失效:

TTL 设置影响
过大(如 86400s)缓存久,查询少;但 IP 变更最多需等 24 小时生效
过小(如 60s)生效快;但查询频繁,延迟高,Resolver 负担重

发布变更最佳实践:变更前 24 小时将 TTL 降至 300s,变更完成、验证无误后再调回大值。

4. CDN 与 CNAME 接入原理

直接配 A 记录时,所有用户解析到同一 IP;CDN 通过 CNAME 劫持解析流程,返回离用户最近的节点 IP:

普通模式:www.example.com  A  → 93.184.216.34(固定 IP)

CDN 模式:www.example.com  CNAME → www.example.com.cdn.net
          www.example.com.cdn.net  → CDN 智能 DNS(根据 GeoIP/负载)
                                  → 就近边缘节点 IP

CNAME 使用限制

  • CNAME 目标必须是域名,不能是 IP
  • 根域名(@/裸域)不可配 CNAME(与 SOA/NS 记录冲突),需用 CNAME Flattening(部分 DNS 服务商支持)或 ALIAS 记录

5. DNS 污染与防御

DNS 污染(Cache Poisoning):攻击者向 Resolver 注入伪造响应,使域名解析到恶意 IP,用户无感知地被劫持。

防御方案原理代价
DNSSEC对 DNS 记录进行数字签名,Resolver 验签后才接受响应部署复杂,增加响应体积,非所有解析器支持
DoH(DNS over HTTPS)DNS 查询走 HTTPS 加密通道,防中间人篡改依赖 HTTPS,首次建连有握手延迟
DoT(DNS over TLS)DNS 查询走 TLS 加密通道(端口 853)防篡改,但不防监听其请求目标(SNI 可见)

考察意图

考察候选人能否区分递归与迭代查询的发生位置,理解 TTL 对线上变更节奏的影响,以及 CDN 依赖 CNAME 实现流量调度的底层逻辑——三者共同构成日常运维和架构设计的必备网络知识。

追问链

  1. 根域名服务器全球只有 13 个 IP,它们扛得住吗?
    "13 个"指 13 个逻辑标识(A-M),实际通过 Anycast 技术部署了数百个物理节点;同时递归 Resolver 大量缓存 TLD 信息,根服务器并不需要参与每次查询,实际负载远低于想象。

  2. 为什么 CDN 用 CNAME 而不直接给 A 记录?
    A 记录是静态 IP,无法根据用户位置动态返回最优节点;CNAME 将解析权转交 CDN 自己的智能 DNS,CDN 可在最后一跳根据 GeoIP、节点健康状态、实时负载返回最优 IP,实现动态就近调度。

  3. TTL 为 0 是否意味着不缓存?实际会如何?
    RFC 定义 TTL=0 表示不缓存,但实际许多 Resolver 出于稳定性会强制设置一个最小缓存时间(如 30s),不严格遵循;同时频繁查询会显著增加解析延迟,极端低 TTL 并不推荐用于生产。

  4. PHP 如何在运行时验证域名是否接入了 CDN?
    调用 dns_get_record($domain, DNS_CNAME) 获取 CNAME 链,遍历每条记录的 target 字段,若后缀命中已知 CDN 域名(如 cloudfront.netfastly.net)即可判定已接入。见代码示例中的 isCdnEnabled()

易错点

  1. 混淆递归和迭代的发生位置:递归发生在客户端到本地 Resolver 这一段(客户端只问一次,等 Resolver 搞定),迭代发生在 Resolver 向根/TLD/权威逐级查询这一段。面试常见失误是把两者全部描述成"客户端依次询问各级服务器"。

  2. 以为 CNAME 可以指向 IP 或用于根域名:CNAME 的目标必须是另一个域名,不能填 IP;根域名(如 example.com)无法配 CNAME,因为 RFC 规定 CNAME 不能与 SOA、NS 等记录共存。

  3. 发布 DNS 变更时忘记提前降 TTL:若 TTL=86400,变更后缓存中的旧记录最长存活 24 小时;正确做法是变更前至少 1 TTL 周期提前降低(如降至 300s),变更完成稳定后再调回。

代码示例

php
// 查询域名的完整 DNS 记录(含 CNAME 链)
function resolveDomain(string $domain): void
{
    $records = dns_get_record($domain, DNS_A | DNS_CNAME | DNS_AAAA);

    foreach ($records as $r) {
        $target = match ($r['type']) {
            'A'     => $r['ip'],
            'AAAA'  => $r['ipv6'],
            'CNAME' => '→ ' . $r['target'],
            default => '?',
        };
        echo sprintf("[%s] %s = %s  TTL=%ds\n", $r['type'], $r['host'], $target, $r['ttl']);
    }
}

resolveDomain('www.example.com');
// 示例输出:
// [CNAME] www.example.com = → www.example.com.cdn.net  TTL=300s
// [A]     www.example.com.cdn.net = 203.0.113.42        TTL=60s

// 判断域名是否已接入 CDN(简易检测)
function isCdnEnabled(string $domain, array $cdnSuffixes = ['cdn.net', 'cloudfront.net', 'fastly.net']): bool
{
    $cnames = dns_get_record($domain, DNS_CNAME);
    foreach ($cnames as $record) {
        foreach ($cdnSuffixes as $suffix) {
            if (str_ends_with($record['target'], $suffix)) {
                return true;
            }
        }
    }
    return false;
}

基于 Apache License 2.0 开源