前端性能优化 - 浏览器 DNS 预解析

123 阅读8分钟

导读

当浏览器从服务器请求资源时,必须先将域名解析为IP地址,才能发出请求,此过程称为DNS解析。DNS解析可以导致请求增加明显的延迟,特别是访问多个跨域服务资源时,延迟可能会大大降低加载性能。本文将介绍如何优化DNS解析的性能。

DNS 预解析简介

到底什么是 DNS 预解析技术,如何使用这项技术,以及应用时需要注意哪些细节呢?下面就一起来了解一下这项技术相关的知识点吧!

什么是 dns-prefetch?

截图.png

dns-prefetch 是一种资源提示伪指令(<link rel="dns-prefetch" />),它会尝试在请求资源之前提前解析域名。解析的域名可能是以后加载的(第三方)文件的域名,也可能是用户尝试点击的(第三方)链接目标的域名。

dns-prefetch 调用方式

《前端性能优化 - 使用 Preload 和 Prefetch 预加载关键资源》一文中介绍过的 prefetch 和 preload 的调用方式类似。dns-prefetch 也是使用 <link/> 标签作为载体,设置 rel 属性为 dns-prefetch,设置 href 属性指定需要 DNS 预取的域名,代码如下:

<link rel="dns-prefetch preconnect" href="//mic-open.mypaas.com.cn">

需要预解析的域名可以写成完整的 URL 地址,也可以用示例中的写法。推荐使用示例中的方式指定域名,这样可以保障域名解析时会与当前站点保持相同的协议(HTTP/HTTPS)。

dns-prefetch 的适用场景和注意事项

并不是所有的跨域资源的访问都需要开发者手动调用 <link rel="dns-prefetch" /> 指令实现 DNS 预解析,浏览器在有时也会自动开发者处理的,所以让我们来看看 dns-prefetch 的适用场景和注意事项。

适用场景

dns-prefetch(DNS预解析)的适用的场景主要是优化访问跨域资源的DNS解析速度,适用场景有以下几类:

  • 页面包含第三方域名的静态资源,可以使用 DNS 预取;
  • 页面的 JS 中会动态生成的链接包含跳转到第三方域名,可以使用 DNS 预取;
  • 重定向跳转的新域名,如:页面有个A域名的链接,但访问A会重定向到B域名的,可以使用 DNS 预取;

注意事项

使用 dns-prefetch 也要注意一点,dns-prefetch 仅对跨域的 DNS 查找有效,因此请避免使用它来指向您的站点本身的域名。这是因为浏览器会自动在背后做本域名下的 DNS 解析。

另外,Chromium 和 Firefox 的关于 DNS Prefetching 的官方文档 有这么一句:

Manual Prefetch

Chromium uses the "href" attribute of hyperlinks to find host names to prefetch. However, some of those hyperlinks may be redirects, for example if the site is trying to count how many times the link is clicked. In those situations, the "true" targeted domain is not necessarily discernible by examining the content of a web page, and so Chromium not able to prefetch the final targeted domain.

可以看到,上面这段文字包含两个重要信息:

  1. chrome 会自动把当前页面的所有带 href 的 link 的 dns 都 prefetch 一遍;
  2. 需要手动添加 link 标签的场景是:你预计用户在后面的访问中需要用到当前页面的所有链接都不包含的域名;

所以,假设页面 head 里面有个第三方的 css 链接(例如CDN资源), 在当前页的 head 里加上对应的手动 dns prefetch 实际上并没有好处。因为上面提到了 chrome 会自动把当前页面的所有带 href 的 link 的 dns 都 prefetch 一遍,所以不用特别手动添加处理这类跨域的资源 DNS 预解析。

dns-prefetch 可以带来多少性能提升?

当浏览器从(第三方)服务器请求资源时,必须先将该跨域的域名解析为IP地址,然后浏览器才能发出请求。此过程称为DNS解析。虽然DNS缓存可以帮助减少此延迟,但是DNS解析可以为请求增加大量的延迟。对于打开了包含许多第三方的连接或资源员的网站,此延迟可能会大大降低加载性能。

屏幕截图 2021-04-21 153836.png

大多数浏览器可以检测并预取页面中包含的超链接中的域名。但是,他们可能找不到由分析和社交共享平台注入的脚本文件和标记中包含的第三方的域名。dns-prefetch 则可以解决跨域DNS解析相关的延迟问题(即,将您的站点域名解析为IP地址所花费的时间),这可能会使站点的页面加载时间减少,从而优化网站性能。

dns-prefetch 的网络消耗是极低极低的,chromium 的官方文档是这么说的:

Each request typically involves sending a single UDP packet that is under 100 bytes out, and getting back a response that is around 100 bytes. This minimal impact on network usage is compensated by a significant improvement in user experience.

翻译:每个请求通常包括发送一个小于100字节的UDP数据包,然后返回大约100字节的响应。用户体验的显著改善弥补了对网络使用的这种最小影响。

Chrome 浏览器使用了 8 个线程专门做 dns prefetching,Chrome 浏览器本身不做 dns 记录的缓存,它是直接从操作系统读 DNS 的。也就是说,直接修改系统的 dns 记录或者 host 是可以直接影响 chrome 的。普遍来说合理的 dns-prefetch 能对页面性能带来 50ms ~ 300ms 的提升。

dns-prefetch 浏览器支持情况

屏幕截图 2021-04-21 161525.png

主流的浏览器都支持了,浏览器对 dns-prefetch 的支持是非常不错了。如果不支持的浏览器遇到 dns-prefetch 提示(或任何其他资源提示),浏览器不会中断资源解析,而只是会忽略它,不会有任何不良后果,大家可以放心的应用 DNS 预解析技术。

DNS 预解析的应用实践

在了解 DNS 预解析的相关技术点后,就需要结合情况分析,看看应该如何利用这项技术来优化站点的(前端)性能。

问题分析

以我公司网站的为例,站点采用是SPA架构。就是说,所有的这些外部跨域资源的访问都是通过 JavaScript 动态创建的才能发起 HTTP 请求,浏览器(例如:Chrome)是无法通过分析 HTML 代码中的各种资源标签自动 DNS 预解析,因此页面所有这些外部资源的DNS预解析都需要手动调用 <link rel="dns-prefetch" /> 伪指令。

按照前文介绍的 dns-prefetch 可以开来多少性能提升 的章节,从理论上分析,如果使用 DNS 预解析后,可以提升的 DNS 解析延迟时间在 300ms (50ms * 6)以上。仅仅一项优化就可以提升300ms的解析速度,应该说是性价比很高了。

最佳实践

DNS 预解析技术的应用落地是比较容易的,只需要在 VUE 项目的 index.html 页面代码的 head 标签中插入以下代码即可:

<link rel="dns-prefetch preconnect" href="//mic-open.xxxx.com.cn" /> 
<link rel="dns-prefetch preconnect" href="//xxx.myxxx.com/" />
<link rel="dns-prefetch preconnect" href="//pkg.mingxxx.com/" />

rel 属性使用了 "dns-prefetch preconnect",而不仅仅是 dns-prefetch。这是因为 dns-prefetch 的浏览器支持比对 preconnect 支持要好。在不支持 preconnect 预连接的浏览器仍然可以回退到 dns-prefetch 来获得更多好处。而使用 preconnect 是因为 preconnect 除了 DNS 预解析外,还会建立于服务器的连接,因此同时使用效果更好。

preconnect.png

举例来说,如果站点是通过 HTTPS 服务访问的,则此过程包括 DNS 解析,建立 TCP 连接以及执行 TLS 握手。使用 rel="dns-prefetch preconnect" 是将两者结合起来可提供进一步减少跨域请求的感知延迟的机会。

另外,还需要注意一点,虽然示例中给多个第三方域的连接都添加了 preconnect 预连接,那是因为这几个域都是比较重要的。如果页面需要建立与许多第三方域的连接,将每个第三方域的连接都使用 rel="dns-prefetch preconnect" 预先连接会适得其反。 因为 preconnect 最好仅用于最关键的连接。对于其它的,只需使用 rel="dns-prefetch" 即可,这样性能会更好,因为可节省一些 DNS 查找的时间。

最后,如果你的站点需要使用很多个第三方的资源,也不是给所有的第三方域名都添加 DNS 预解析处理,需要根据第三方站点的重要性添加,并且最好控制在 5 个域名以内。

总结

还是那句话,前端性能优化是一个系统化的工程,并不能一蹴而就,需要持续优化。作为前端工程师,则需要持续学习和关注新技术,充分了解新技术的核心原理,做好知识积累。结合自身产品的实际产品情况,将新技术应用到我们的产品,给用户带来更好使用体验。