操作系统|[译]HTTP/2的优先级

原文地址:HTTP/2 Prioritization
原文作者:[Patrick Meenan]
译文出自:FE-star/speed
译者:smallbonelu
以正确的顺序请求页面资源对于快速的用户体验至关重要。想象一下,如果一个网页上有一堆图片,还有一个外部样式表,一些自定义Web字体和一些在head中的脚本。如果浏览器首先下载了所有图片并且最后加载了样式表,在所有内容都加载完毕前,页面将完全是空白页。如果浏览器首先加载了所有阻塞资源,接着是Web字体和图片,那么它可以更早地呈现页面,并让用户开始看到内容,同时加载其余的图片。我在Chrome浏览器性能工作上的大部分时间都花在了尝试优化加载资源的顺序以获得最佳用户体验上。
使用HTTP/1.x,浏览器可以完全控制资源加载顺序。每个连接一次只能支持一个资源请求,服务器会尽快返回请求的内容。浏览器可以通过决定何时请求资源以及打开多少个并行连接来安排请求。
HTTP/2让这些事情变得更好也更复杂了。浏览器可以一次请求多个资源,指定一些优先级信息来帮助确定应该如何处理这些资源,然后等待服务器发回所有数据,而不是一次请求一个。如果浏览器和服务器都支持优先级,则应使用浏览器指定的规则并使用所有可用带宽来传递资源,而不会有资源之间的相互竞争。
每个资源都获取一个stream ID来标识连接上的资源,并且有三个参数用于定义资源优先级:
  1. 父级数据流(Parent Stream):这个数据流是一个“依赖”资源或者应该在之后被传递的数据流。有一个所有数据流共享的虚拟root stream 0
  2. 权重(Weight):1到256之间的数字,用于标识在多个数据流共享连接时分配给此数据流的带宽量。带宽是相对于所有其他活动的数据流的权重分配的,而不是绝对值。
  3. 独占位(Exclusive bit):一个标志,表示应该在不与任何其他数据流共享带宽的情况下下载。
浏览器不一定同时知道所有资源,因此服务器能够在新请求到达时重新确定请求的优先级也很关键。
那么......我们是怎么做的呢?
浏览器引擎 Chrome
这包括使用Chromium的优先级逻辑和网络堆栈的所有内容。
【操作系统|[译]HTTP/2的优先级】Chrome是唯一使用独占位的浏览器,它在每个资源上使用它。它构建了一长串资源,将较低优先级的资源链接到仍在等待的最后一个相同或更高优先级的资源。如果没有更高优先级的资源挂起,则启动新链。权重以静态映射的方式分配,Chrome的五个内部优先级分别对应相应的权重(即HIGHEST为256)。


假设给定几个请求队列,所有请求都设置了独占位,服务器将选择权重最高的一个,完成传递后,就将其从列表中弹出并重新选择。
假设Chrome正确构建了排序,这可能非常有效。独占下载资源是样式表和脚本等阻塞资源的最佳选择。当涉及到图片和视频时,你可能需要一些交错执行的任务(特别是对于渐进式图片)。否则,在转到下一个请求前,你将要等待每个图片或视频完全下载下来。
Firefox
Firefox实现了HTTP/2的树结构,并构建了一个虚拟的数据流树,用来对不同请求类型进行分组。Firefox对分组进行了加权,以便为更重要的组提供更多的带宽,并且当所有节点都已完成时,空闲周期(idle cycles)可用于响应。
在根级别中,有一个“leader”组,,它的带宽是“Other group”的两倍。在“leader”组中,有一个叫“follower”的子组,它只有在“leader”组的直系后代完成下载后才会开始下载。例如,在权重为200的“leader”组下面,一旦所有CSS完成下载后,图片和字体才会开始下载。一个组内的所有子资源具有相同的权重并均匀分配带宽(同时下载所有图片或所有脚本)。


从整体结构上来看Firefox非常出色,可以为推测请求定义空闲周期,但对同时下载所有资源优先级的划分并不是很好。像样式表和脚本等这样的阻塞资源的优先下载要比按顺序下载它们才能让解析器处理文档要好。
Safari
这包括iOS上的所有浏览器(包括Chrome)。
Safari采用了一种非常简单的方法,看起来是SPDY优先级的遗留部分。五个内部webkit优先级静态映射到权重,并且没有定义依赖关系。所有请求基于每个资源的优先级来划分带宽权重进行同时下载(例如,脚本获得图片带宽的三倍)。


这样导致高并发性并不是很好,而且落后于Firefox的实现,在Firefox中至少follower组的资源会等到leader组资源全部完成任务之后(尽管它可能不足以证明Firefox中树的复杂性)。
Microsoft Edge / Internet Explorer
简而言之,Microsoft Edge(和Internet Explorer)根本不支持优先级。所有请求均匀分配带宽(几乎是最糟糕的情况)。


Servers/Hosting/CDNs 现在大多数服务器都支持HTTP/2了,通常也"支持"优先级的。支持加了引号,是因为即使一个服务器内部支持资源优先级,实际上让它能够与浏览器工作也需要调整网络堆栈并尽可能减少输出缓冲而不影响吞吐量。
缓冲可能是一个问题,因为服务器可以发送一堆低优先级的响应,这些响应在高优先级响应到来之前已经在缓冲区中排队。发送高优先级响应时,无法抢占已缓冲的低优先级响应。缓冲可以来自服务器本身,TLS层,TCP发送的缓冲,甚至来自网络上的bufferbloat,跟踪并消除所有多余的缓冲可能会很复杂。我在今年早些时候的一篇博文中谈到了一些原因和解决方案,但这并不是一个详尽的清单。
为了测试服务器优先级的有效性,我构建了一个测试页面,你可以在你的服务堆栈上部署该测试页面以查看优先级是否正常工作。它专门针对Chrome的优先级逻辑,因此最好使用慢速连接上的Chrome进行测试。它先将3MB低优先级图片排队,然后在下载并执行高优先级脚本后,脚本会发送4个高优先级请求(一张图片,一个页面背景,一个自定义的webfont和一个阻塞脚本)。当优先级正常工作时,后置的高优先级请求的资源会跳过低优先级请求并快速得到响应:


当优先级工作不正常时,部分或全部后置的高优先级请求的资源会被延迟,直到优先级较低的请求完成为止:


后置请求的阻塞脚本的延迟超出了“DOM Content Loaded”的度量值,字体和2个图片的延迟对视觉体验产生了相当大的影响:


我们从哪里开始? 为了跟踪CDN和托管服务提供商支持HTTP/2优先级的程度,Andy Davies创建了一个GitHub仓库,用于跟踪当前的支持状况,任何人都可以提交测试结果来群策群力。在撰写本文时,情况非常糟糕,只有两个CDN确实正确地确定了优先级,并且存在一些非常令人震惊的失败(例如每个云提供商甚至Google的GFE)。希望通过提高对这种情况的认知,我们将能够为优先级提供更广泛的支持。
对托管和服务器来说,好消息是你总是可以在它们之前配置一个支持优先级的CDN来解决问题(尽管直接支持它会很好)。
在浏览器方面,除了敦促浏览器厂商以获得更好的支持之外,没有太多可以做的事情。其中的一些厂商可能会遇到架构问题,如他们的浏览器引擎在操作系统的网络堆栈层之上,导致无法传递优先级信息。可能是我的偏见,但我认为Chrome是最接近“正确”的做法,但仍有相当大的改进空间。
HTTP/3也即将到来,但目前的优先级方案不会改变。这个改变是网络堆栈的终结。在服务器端,这意味着操作系统的缓冲和拥塞控制不再起作用,服务器软件100%负责最小化缓冲(包括拥塞控制算法以最小化缓冲区)。
那么就说到这里,希望为了HTTP/2和一个安全,高性能的网络,我们可以在2019年修复资源优先级。

    推荐阅读