技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTETC

DNS 预解析

所属馆藏
Other
文件格式
Markdown
原始路径
Other/03-DNS 预解析
本文目录9 个章节
  1. 一、原理
  2. 二、如何使用
  3. 三、如何验证效果
  4. 四、使用场景与限制
  5. 五、项目中使用 DNS 预解析
  6. 六、域名发散和域名收敛
  7. 七、历史浏览器实验资料
  8. 八、结论
  9. 参考资料与历史来源

DNS 预解析

Category(分类): Other Status: 已核查

本文保留原文的历史示例和截图,但浏览器调度、DNS 缓存以及网络协议会随实现和环境变化。下面的固定毫秒数、线程数和浏览器内部页面只作为历史资料,不能作为性能承诺。

一、原理

域名系统 - 维基百科,自由的百科全书

DNS(Domain Name System)是一个分布式命名系统,把域名映射到 IP 地址等记录。一次 Web 请求可能涉及多个不同阶段:DNS 解析、建立 TCP 或 QUIC 连接、TLS 握手(HTTPS)、发送 HTTP 请求和接收响应。DNS 预解析只针对第一阶段,不会自动建立 TCP、TLS 连接,也不会缓存页面内容。

客户端与 DNS 服务器各做什么

浏览器通常把解析请求交给操作系统解析器;浏览器和操作系统可能有自己的缓存,家庭路由器也可能提供转发或缓存。递归解析器代表客户端向 DNS 层级查询,并利用 TTL、负缓存等规则缓存结果;在需要继续查询时,递归解析器通常分别向根、顶级域(TLD)和权威 DNS 服务器发起迭代查询,而不是由浏览器按固定顺序逐个访问。

可以用下面的概念链路理解一次缓存未命中的递归查询:

浏览器/应用缓存
  → 操作系统解析器与本地缓存
  → 配置的递归解析器(可能是运营商、企业或公共 DNS)
  → 根服务器 → TLD 服务器 → 权威 DNS 服务器
  → 递归解析器缓存结果 → 返回客户端

实际实现可能使用本地代理、DoH(DNS over HTTPS)、DoT(DNS over TLS)、IPv4/IPv6 并行查询或其他策略。TTL 到期后通常需要重新查询或获取记录;不存在的记录也可以按 SOA 中的规则进行负缓存(参见 RFC 2308)。

什么是 DNS 预解析

dns-prefetch 是浏览器的资源提示(hint):页面作者告诉浏览器某个跨源主机名很可能稍后会用到,浏览器可以提前尝试解析。浏览器可以执行、延后、部分执行或忽略这个提示;解析结果保存在哪里、保存多久由浏览器、操作系统和 DNS 实现决定,不能保证一定写入“系统缓存”。

它只减少潜在的 DNS 查找等待,不保证请求一定命中缓存,也不保证一定带来可测收益。第三方域名越多,产生的 DNS 查询、连接尝试、隐私暴露和失败面也越多,所以应只提示确实即将使用且收益明确的源。

二、如何使用

1、手动添加解析提示

在 HTML 的 <head> 中添加:

<link rel="dns-prefetch" href="//cdn.example.com">

协议相对 URL 是历史写法;在现代 HTTPS 站点中也可以明确写成:

<link rel="dns-prefetch" href="https://cdn.example.com">

它只指定主机名即可,路径不会带来额外的 DNS 意义。不要为当前页面同源主机重复添加,也不要批量枚举所有潜在域名。

2、dns-prefetchpreconnect 的区别

两者不等价

  • dns-prefetch:仅提示提前进行 DNS 解析;
  • preconnect:提示提前建立连接,通常包含 DNS、TCP,HTTPS 还包括 TLS;HTTP/3 场景可能涉及 QUIC 连接。
<!-- 只预解析,适合可能稍后使用但尚不值得建连接的源 -->
<link rel="dns-prefetch" href="https://images.example.com">

<!-- 少量关键跨源,可能马上请求时才考虑预连接 -->
<link rel="preconnect" href="https://fonts.example.com" crossorigin>

preconnect 会消耗连接、TLS 和服务器资源,应只用于少量关键源;普通的跨源资源优先考虑 dns-prefetch,并以真实性能数据决定是否保留。

3、打开和关闭自动预解析

历史上可以使用非标准的 X-DNS-Prefetch-Control 响应头,或兼容性较好的 HTML 元信息:

X-DNS-Prefetch-Control: on
<meta http-equiv="x-dns-prefetch-control" content="on">

该控制手段不是现代 Web 标准,浏览器支持和行为存在差异,不应作为主要性能方案。off 可在确实不希望浏览器对页面中的链接进行预解析时作为兼容性提示,但不能把它当成对所有网络活动的通用隐私控制。字段名必须是 X-DNS-Prefetch-Control,不能写成错误的 X-DNS_prefetch-Control

浏览器也可能基于自身策略自动预解析链接或资源。不要依赖某个版本的 Chromium 线程数量,也不要把 HTTPS 页面的自动行为绝对描述为“永远关闭”或“永远开启”;作者只能提供提示,最终由浏览器决定。

三、如何验证效果

历史文章曾使用 WebPageTest 和淘宝页面观察 DNS Lookup。截图仍保留作案例,但线上页面、节点、浏览器和网络环境已经变化,不能复现为固定结论。

历史 WebPageTest DNS 计时示例

历史淘宝页面及 dns-prefetch 标签示例

验证时可以在浏览器开发者工具 Network 面板查看请求 Timing,也可以使用 Performance API、PerformanceResourceTimingServer-Timing 和真实用户监控,分别比较:

  • DNS lookup 是否发生、耗时多少;
  • TCP/QUIC 连接与 TLS 是否复用;
  • 首字节时间、下载时间和整体 LCP 是否改善;
  • 不同地区、运营商、设备、IPv4/IPv6 和冷/热缓存下是否一致。

不要用“本地缓存 0—1ms、运营商缓存 80—120ms、总能节省 15—300ms”之类的数字承诺收益。DNS 延迟取决于网络、递归解析器、缓存命中、DoH/DoT、协议和服务端配置,必须用自己的数据验证。

上方第二张截图还保留了原文对淘宝页面及其 dns-prefetch 标签的观察;页面内容、域名和浏览器行为均属于历史资料。

原文还展示了开启和关闭提示时的多个 Network Timing 截图。它们只能说明“某次测试中 DNS 阶段可能已经提前完成”,不能证明连接和 TLS 也被提前建立:

历史开启预解析的页面示例

历史开启预解析的 Timing 示例

历史关闭预解析的页面示例

历史关闭预解析的 Timing 示例

历史 preconnect 的 Timing 示例

四、使用场景与限制

适合考虑 dns-prefetch 的场景包括:

  1. 首屏很快会请求的静态资源 CDN、图片域名或字体域名;
  2. 用户操作后几乎必然跳转、且跳转目标是跨源的页面;
  3. 已确认 DNS 查找是关键路径,且第三方源稳定可信。

不适合或需要谨慎的场景包括:

  • 为所有超链接、所有构建产物或大量随机域名添加提示;
  • 用隐藏 iframe 批量触发解析。该做法会制造隐藏请求和额外流量,可能泄露用户正在浏览的站点,也不受浏览器保证;
  • 为仅在很晚才使用、低命中率或可能根本不用的第三方域名预解析;
  • 把 DNS 预解析当成缓存刷新、连接预建立、内容预取或隐私保护方案。

登录页、搜索结果页、商品页等可以是候选场景,但“新用户一定收益更大”不是普遍规律,应按用户路径和 RUM 数据验证。跨源字体、脚本和图片还要正确配置 CORS、CSP,并评估第三方供应链风险。

五、项目中使用 DNS 预解析

通常应在源码中为少量已知关键源添加明确的 <link>。如果确实需要在构建时收集外链,应使用 URL 解析器而不是宽松正则,并设置允许列表,避免把用户内容、示例代码、查询参数中的域名误当成资源源。

下面保留原文的 Vite 构建思路,并修正 vite bulid 拼写;实际项目仍应根据构建工具和 CSP 策略调整:

{
  "scripts": {
    "build": "vite build && node ./scripts/dns-prefetch.cjs"
  }
}

一个更安全的简化示例(只处理允许的外部源):

// scripts/dns-prefetch.cjs
const fs = require('node:fs')
const path = require('node:path')

const dist = path.resolve('dist')
const allowedHosts = new Set([
  'cdn.example.com',
  'images.example.com'
])
const hostPattern = /https?:\/\/([^/"'\s)]+)/g
const hosts = new Set()

function walk(dir) {
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
    const file = path.join(dir, entry.name)
    if (entry.isDirectory()) walk(file)
    else if (/\.(html|css|js)$/i.test(entry.name)) {
      const source = fs.readFileSync(file, 'utf8')
      for (const match of source.matchAll(hostPattern)) {
        try {
          const host = new URL(match[0]).hostname
          if (allowedHosts.has(host)) hosts.add(host)
        } catch {
          // 忽略无法解析的文本
        }
      }
    }
  }
}

walk(dist)
const links = [...hosts]
  .map(host => `    <link rel="dns-prefetch" href="https://${host}">`)
  .join('\n')

for (const name of fs.readdirSync(dist)) {
  if (!name.endsWith('.html')) continue
  const file = path.join(dist, name)
  const html = fs.readFileSync(file, 'utf8')
  fs.writeFileSync(file, html.replace(/<head>/i, `<head>\n${links}`))
}

很多框架已经能根据资源依赖生成合理的连接提示,优先检查现有构建产物和浏览器行为。不要把所有外链自动加入 head,也要确认注入内容不会破坏 CSP、HTML 转义或缓存。

六、域名发散和域名收敛

历史域名收敛示意图

1、域名发散

在 HTTP/1.1 时代,为绕过单域名连接并发限制,网站曾把静态资源分到多个子域名,这就是域名发散。它会增加 DNS、TCP/TLS(或 QUIC)连接以及连接管理成本,还可能降低缓存和连接复用效率。

2、域名收敛

HTTP/2 和 HTTP/3 支持在一条连接上多路复用,现代实践通常避免无理由的域名发散,并尽量让同一站点复用连接。HTTP/2 的请求仍共享 TCP,HTTP/3 基于 QUIC,不能简单地把二者都描述成“强制 SSL”;HTTPS、HTTP/2、HTTP/3 的启用方式和服务端配置各有要求。

连接建立过程可以概括为:

DNS 解析 → TCP 或 QUIC 建连 →(HTTPS 时)TLS → HTTP 请求/响应

这并不意味着“域名越少越快”:不同安全边界、CDN、Cookie、组织或故障隔离需求可能需要不同域名。原则是减少不必要的第三方源和连接,在关键路径上使用连接复用,并用浏览器 Timing 与真实用户数据验证。

七、历史浏览器实验资料

下面的图片来自原文引用的旧版浏览器资料,保留用于理解当时的实验环境。现代浏览器可能已经移除相应内部页面或改变调度策略;Chrome 曾经使用的异步线程数量、DNS 缓存容量和自动解析规则都不应写成通用结论。

历史 DNS 解析流程图

历史浏览器支持情况

历史 DNS 预取队列统计

历史 DNS 统计示例

八、结论

DNS 预解析只是一个可被浏览器忽略的优化提示。它最适合用于少量、确定会很快使用的跨源主机名;preconnect 是更重的连接提示,不能与 dns-prefetch 混同。性能优化的目标不是让 DNS 查询数字看起来更小,而是降低真实用户关键路径的延迟,同时控制流量、隐私和第三方依赖风险。

参考资料与历史来源

历史截图:淘宝/WebPageTest、Chrome 内部统计和旧版浏览器设置仅用于说明当时的观察方法;chrome://dnschrome://net-internals/#dns 等页面可能已在新版本中移除或改变,不能作为跨浏览器调试方法。

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS