DNS解析优化

5/17/2023 性能优化

# 前言

网络通讯大部分是基于 TCP/IP 的,而 TCP/IP 是基于 IP 地址的,所以计算机在网络上进行通讯时只能识别如“202.96.134.133”之类的 IP 地址,而不能认识域名。

而我们在浏览器输入常见的域名能看到页面,就是有一个 DNS 服务器,将域名翻译成 IP 地址,然后调出 IP 地址所对应的网页。

当我们在浏览器地址栏输入 url 后,就会像前端服务器请求 html 文档,当请求构建是后,就会经过 DNS 域名解析,典型的一次 DNS 解析需要耗费 20-120 毫秒,通常这种情况下的 DNS 解析我们是无法优化的,但是如果在我们项目中外链了其他域名下的文档,在项目加载时,也是需要去解析域名,这一部分是可以做到优化的

# 简单分析

DNS 是有本地缓存的,如果我们解析过当前域名,就会将其缓存在浏览器,下一次遇到这个域名时,就不需要再解析直接返回对应的 IP 了。如果我们在项目文件(js、css、html 等)加载时,异步解析 DNS,就可以在后续遇到其他域名时,直接获取对应的 IP。

dns-prefetch即 DNS 预获取,DNS Prefetching 是让具有此属性的域名不需要用户点击链接就在后台解析,而域名解析和内容载入是串行的网络操作,所以这个方式能 减少用户的等待时间,提升用户体验。

<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8" />
    <title>SunRain</title>
    <meta name="description" content="记录学习笔记" />
    <meta
      name="viewport"
      content="width=device-width,initial-scale=1,user-scalable=no"
    />
    <!-- dns预解析 -->
    <link rel="dns-prefetch" href="www.baidu.com" />
    <link rel="dns-prefetch" href="//g.tbcdn.cn" />
  </head>
  <body></body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

我们是能够知道项目中用到了哪些其他域名,那么我们可以在对应的 html 中添加dns-prefetch,来对这些域名进行预解析。

# 项目构建中实现

虽然我们可以在项目中手动加入要解析的域名,但是我们在开发过程中,很难在每次有新的域名资源添加时想起去添加对应的dns-prefetch域名,同时在多人协作开发项目时,不同的人可能会有不同的开发习惯,那么这种添加方式就过于简单了。

我们可以用 node 写一个 js 文件,在项目每次打包后去分析打包后的文件,获取项目中遇到的域名,将其 DNS 预解析插入到 index.html 中,这样我们在项目中添加的域名,就会自动生成 DNS 预解析了,所以我们可以在项目中添加一个dns-prefetch.js来实现这个

const fs = require("fs");
const path = require("path");
const { parse } = require("node-html-parser");
const { glob } = require("glob");
const urlRegex = require("url-regex");

// 获取外部链接的正则表达式
const urlPattern = /(https?:\/\/[^/]*)/i;
// 为了保证域名不重复,所以使用set
const urls = new Set();
// 遍历dist目录中的所有HTML、js、css等文件
async function searchDomain() {
  const files = await glob("dist/**/*.(html,css,js)");
  for (const file of files) {
    // 读取文件内容
    const source = fs.readFileSync(file, "utf-8");
    // 用urlRegex分析内容中的所有地址
    const matches = source.match(urlRegex({ strict: true }));
    if (matches) {
      matches.forEach((url) => {
        // 获取匹配的域名
        const match = url.match(urlPattern);
        if (match && match[1]) {
          // 将域名添加到集合中
          url.add(match[1]);
        }
      });
    }
  }
}

async function insertLinks() {
  // 读取所有html文件
  const files = await glob("dist/**/*.html");
  // 将之前获取的域名地址转换成link标签
  const links = [...urls]
    .map((url) => `<link rel="dns-prefetch" href="${url}" />`)
    .join("\n");

  // 遍历所有html文件
  for (const file of files) {
    // 读取内容
    const html = fs.readFileSync(file, "utf-8");
    // 使用parse将内容转译成节点
    const root = parse(html);
    // 获取head节点
    const head = root.querySelector("head");
    // 将生成的link标签插入好head中
    head.insertAdjacentHTML("afterbegin", links);
    // 将内容写回文件
    fs.writeFileSync(file, root.toString());
  }
}

async function main() {
  await searchDomain();
  // head中添加DNS预获取标签
  await insertLinks();
}

main();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

主要就是步骤如下:

  • 读取打包后 dist 目录下的文件,
  • 将其中的外链地址利用正则获取,并获取对应的域名地址,
  • 用这个域名地址生成对应的 dns 预获取 link 标签
  • 读取 dist 目录下的 html 文件,获取<head>标签
  • 将生成的 link 标签插入到<head>标签中,再写入到原来的 html 文件中

# 注意事项

默认情况下浏览器会对页面中和当前域名(正在浏览网页的域名)不在同一个域的域名进行预获取,并且缓存结果,这就是隐式的 DNS Prefetch。如果想对页面中没有出现的域进行预获取,那么就要使用显示的 DNS Prefetch 了。

Chrome 和 Firefox 3.5+ 内置了 DNS Prefetching 技术并对 DNS 预解析做了相应优化设置。所以即使不设置此属性,Chrome 和 Firefox 3.5+ 也能自动在后台进行预解析。

虽然使用 DNS Prefetch 能够加快页面的解析速度,但是也不能滥用,因为有开发者指出禁用 DNS 预读取能节省每月 100 亿的 DNS 查询 。

如果需要禁止隐式的 DNS Prefetch,可以使用以下的标签:

<meta http-equiv="x-dns-prefetch-control" content="off" />
1
Last Updated: 1/21/2025, 10:16:53 AM