AndreGeng/AndreGeng.github.io

性能优化 -- dns-prefetch

AndreGeng opened this issue · 2 comments

问题

DNS解析可以导致一个用户可感知的延迟时间. DNS解析消耗的时间有很大的变化范围一般从1ms(对于那些本地缓存的结果)到几秒.

解决方案

DNS预解析尝试用户在点击一个超链之前就解析它的域名. 这是通过计算机的常规DNS解析机制实现的; 一旦一个域名被预解析之后, 当用户访问这个链接时就不会感到明显的延时. 预解析效果最明显的一个例子是, 搜索引擎的搜索结果页(一般都包含很多指向不同域名的链接). 当我们遇到页面里面的超链时, 我们可以预解析这些超链的域名. 这些工作都是和用户浏览页面并行进行的, 只占用少量的CPU和网络资源. 当用户点击那些超链时, 这通常能节省200ms的时间(假设用户最近没访问过这个域名). 而更重要的是, 预解析还能避免用户遭遇DNS解析时的最坏情形(那通常会占用1s以上的时间).

结构设计

Chromium DNS预解析的实现不使用浏览器的网络. 它依赖外部线程去解析, 并把结果存入操作系统的缓存中, 这么做的好处是它能兼容任何的网络(因为它是外部的), 同时也避免了偶然性对主网络的影响.
因为有些DNS解析会很慢, 所以防止一次解析影响其它解析就显得很重要. 为实现这个目的, Chromium采用8个异步线程来负责DNS预解析(在windows下, 没有DNS异步解析的原生支持). 这些线程排成一个队列去领取待解析的域名, 当它取得域名解析的返回结果, 就把它存入操作系统缓存并把自己重新排入队列. 由于采用8个线程, 我们很少会看到超过1或2个线程被大量block, 而且大多数的解析过程是相当快的. 在Debug模式版本下, "about:histograms/DNS.PrefetchQueue"有队列延时的状态.

手动预解析

Chromium读取超链的href属性来获得需要预解析的域名, 对于一些重定向的网页, Chromium不能做到预解析最终的域名.
这时可以采用手动参见解析, 在页面加入如下代码:

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

上面的代码不会对当前页面的渲染造成影响, 但会让Chromium去预解析"host_name_to_prefetch.com", 就好像页面上有一个到"host_name_to_prefetch.com"域名的超链一样.

DNS 预解析控制

默认情况下, Chromium不会预解析https站点页面中出现的各个域名. 这个限制主要是为了防止偷听者通过DNS预解析的流量去推测HTTPS页面上出现的超链域名. 唯一的例外是, Chromium可能会周期性的重解析HTTPS页面自身的域名.
一个好奇的内容作者(例如, 博客上的一个评论者)有可能会滥用DNS预解析去尝试推断包含链接的曝光情况. 你可能想阻止这种滥用行为.
Chromium包含了一个DNS预解析控制机制, 它允许打开HTTPS页面的自动预解析, 或是关闭HTTP页面的自动预解析.
Chromium会观察http header里的"X-DNS-Prefetch-Control"字段(它的值为'on'/'off'), 来打开/关闭预解析行为. 页面内部可以通过"meta http-equiv"标签来更改预解析行为. 如果页面在之前关闭了预解析行为, 后续的打开行为会被忽略.
例如, 下面的页面来自"https://content_author.com/", 最终的结果是chromium会预解析'b.com', 但不会预解析'a.com', 'c.com'和'd.com'

<a href="http://a.com"> A) Default HTTPS: No prefetching </a>
<meta http-equiv="x-dns-prefetch-control" content="on">
<a href="http://b.com"> B) Manual opt-in: Prefetch domain resolution. </a>
<meta http-equiv="x-dns-prefetch-control" content="off">
<a href="http://c.com"> C) Manual opt-out: Don't prefetch domain resolution </a>
<meta http-equiv="x-dns-prefetch-control" content="on">
<a href="http://d.com"> D) Already opted out: Don't prefetch domain resolution. </a>

页面内部的frames会继承父页面的预解析设定. DNS预解析控制只对页面内的超链接起作用, 对手动预解析不起作用.

浏览器启动

Chromium启动时会自动预解析上次解析的前10个域名. 这通常能节省用户200ms左右的时间.

地址栏

用户在搜索时, 也会把自动补全的那些域名进行预解析, 这通常能节省用户100ms左右的时间.

有效性

典型使用

如果用户最近解析过一个域名, 操作系统会缓存解析结果, 再次解析这个域名的时间就会低至0-1ms. 如果要解析的域名没有本地缓存, 需要进行网络请求, 那最快的情形(当最近的路由/防火墙有缓存时), 大概花费15ms. 一些常用的域名像google.com/yahoo.com在一些本地ISP提供商那里会有缓存, 这种情形下大概会花费80-120ms. 如果要解析的域名不太常见, 那解析过程就要在DNS resolver的层级中上下搜索, 一般会花费200-300ms. 更有趣的是, 当遇到一些像网络较差, DNS服务器忙的情况时, 解析过程可以会花上1~10s的时间.

网络使用

DNS解析请求非常轻量, 一个请求一般只包含单一的UDP package, 大概100 bytes左右, 返回的响应也差不多100 bytes大小.

缓存驱逐

本机的DNS缓存是很有限的. 一台window xp上的缓存数量大概是50-200条. 这样的话新的解析条目会覆盖旧的解析条目. Chromium会对下层的缓存建模, 猜测一条被删除的条目可能'即将被使用'. 当它遇到这种情况, 会再次解析这个条目, 或把它标识为"最近使用". 这种行为可能对那些没使用预解析的应用有消极的影响.
大多数站点会设定域名解析的失效时间为5mins.(他们设置这个较短的时间, 是为了适应自己系统的变更) 这也会导致缓存驱逐.
"about:histograms/DNS" and "about:dns" 包含DNS预解析活动更多的信息.

翻译的比较拙劣, 原文见
DNS Prefetching