HTTP/2承诺了一个更快的网络,而Cloudflare很久以前就为我们所有的客户推出了HTTP/2访问。但是HTTP/2的一个特性——优先级,并没有达到预期的效果。问题不在于它从根本上被破坏了,而在于浏览器实现它的方式。
现如今,Cloudflare正在推动对HTTP/2优先级的改进,使我们的服务器能够控制优先级决策,从而真正大幅提高网络速度。
历史上,浏览器一直控制着网站内容的加载方式和加载时间。今天,我们将彻底改变所有付费计划的模式,将控制权直接交给网站所有者。客户可以在Cloudflare控制面板的Speed选项卡中启用“增强的HTTP/2优先级”:该选项卡使用改进的调度方案覆盖了浏览器的默认设置,从而显著提高了访问者的体验(我们已经在多个场合看到50%的速度提升)。有了Cloudflare Workers,站点所有者可以更进一步,完全根据他们的具体需求定制访问体验。
背景
网站页面由数十个(有时候是数百个)独立的资源组成,浏览器将这些资源加载并组装到最终显示的内容中。这包括用户与之交互的可见内容(HTML、CSS、图像)以及站点本身的应用程序逻辑(JavaScript)、广告、跟踪站点使用情况的分析和市场跟踪指向标。加载这些资源的顺序对用户查看内容和与页面交互所需的时间有很大影响。
浏览器基本上是一个HTML处理引擎,它遍历HTML文档,按照从HTML开始到结束的顺序执行指令,并在此过程中构建页面。对样式表(CSS)的引用告诉浏览器如何设置页面内容的样式,浏览器将延迟显示内容,直到完成样式表的加载(这样它便知道如何设置将要显示的内容的样式)。文档中引用的脚本可以有几种不同的行为。如果脚本被标记为“async(异步)”或“defer(延迟)”,则浏览器可以继续处理文档并运行脚本代码(只要脚本可用)。如果脚本没有被标记为“async(异步)”或“defer(延迟)”,那么浏览器必须停止处理文档,直到脚本被下载和执行之后才能继续。这些脚本被称为“阻塞”脚本,因为它们阻止浏览器继续处理文档,直到它们被加载和执行。
HTML文档分为两部分。在文档的开头,包含浏览器显示内容所需的样式表、脚本和其他指令。文档的位于标题之后,它包含在浏览器窗口中显示的实际页面内容(然而脚本和样式表也可以在body中显示)。在浏览器处理到文档主体之前,用户看不到任何显示内容,页面将保持空白,所以尽快通过文档头部是很重要的。如果你想更深入地了解细节,“HTML5 rocks”将提供一个很棒的教程,介绍浏览器的工作原理。
浏览器通常负责确定加载构建页面以及继续处理文档所需的不同资源的顺序。在使用HTTP / 1.x的情况下,浏览器一次性向一台服务器请求的内容数受限(通常为6个连接,而每个连接一次只能请求一个资源),因此,浏览器按事物的需求严格控制着加载顺序。而使用HTTP / 2,事情会有很大改变。浏览器可以立即请求所有资源(至少可以在知道这些资源后立即请求),并且它向服务器提供了有关如何交付资源的详细说明。
最佳资源排序
在页面加载的大部分环节中,我们可以找到一种对资源的最优排序,从而带来最快的用户体验(并且优化与不优化之间的差异可能很大——改善幅度高达50%或更多)。
如上所述,在页面加载周期的早期(浏览器呈现任何HTML的部分中CSS和阻塞JavaScript上的内容之前),最好使用100%的连接带宽来下载阻塞资源,并按照它们在HTML中定义的顺序一次下载一个资源。这使得浏览器在下载下一个阻塞资源时可以解析和执行每一项,从而使下载和执行形成流水线。
这些脚本无论是并行下载还是依次下载都需要相同的时间,但是通过顺序下载,浏览器可以在下载第二个脚本时处理并执行第一个脚本。
当渲染阻塞内容加载完成,事情就变得有趣起来了,并且最佳加载顺序可能取决于特定的站点或者甚至是业务优先级(用户内容、广告、分析等)。特别是字体部分可能会很困难,因为浏览器仅在将样式表应用于将要显示的内容之后才发现所需的字体。所以当浏览器知道字体时,它需要将已经准备好的文本绘制到屏幕上。加载字体时的任何延迟都会导致屏幕上出现空白文本(或使用错误的字体显示文本)。
通常我们需要考虑一些权衡因素:
页面(视窗)可见部分中的自定义字体和可见图像应尽快加载。它们直接影响用户对页面加载的视觉体验。
相较于其他JavaScript资源,非阻塞的JavaScript应该进行串行下载,这样每次的执行与下载内容就可以形成流水线。JavaScript可能包括面向用户的应用程序逻辑以及分析跟踪和市场指向标,对它们的延迟会导致业务跟踪的指标下降。
图像可以从并行下载中获得优势。其文件的前几个字节可能包含浏览器布局所需的图像尺寸,逐行图像并行下载在传输了大约50%的字节后看起来就是完整的了。
权衡利弊之后,在大多数情况下行之有效的一种策略是:
自定义字体下载顺序,并根据可见图像拆分可用带宽。
可见图像并行下载,拆分其中的“图像”共享带宽。
当没有更多的字体或可见图像等待加载时:
非阻塞脚本按顺序下载并使用非可见图像拆分可用带宽。
非可见图像并行下载,拆分其中的“图像”共享带宽。
通过这种方式,浏览器可以尽可能快地加载对用户可见的内容,尽可能减少应用程序逻辑延迟,并且以最快完成布局的方式加载不可见图像。
案例
为了便于说明,我们将使用来自典型电子商务网站的简化产品类别页面。在这个例子中,页面有:
页面本身的HTML文件,由蓝色框表示。
1个外部样式表(CSS文件),以绿色框表示。
4个外部脚本(JavaScript),以橙色框表示。页面开头有2个脚本处于阻塞状态,而另外2个脚本是异步的。阻塞脚本的框使用了较深的橙色阴影。
1种自定义Web字体,用红色框表示。
13张图片,以紫色框表示。其中页面徽标和4个产品图像在视图中可见,还有8个产品图像需要滚动才能看到。这5个可见图像均使用较深的紫色阴影框表示。
为了简单起见,我们将假定所有资源的大小均相同,并且每个资源都需要1秒钟才能下载到访问者的连接上。加载所有内容总共需要20秒,但是加载的方式会对体验产生巨大影响。
这就是资源加载时我们所描述的最佳加载方式在浏览器中的样子:
在加载HTML,CSS和阻塞脚本的前4秒钟,该页面为空白,这些都使用了100%的连接。
在4秒标记处,页面的背景和结构将显示出来,不带文本或图像。
一秒后,在5秒时,页面的文本显示出来。
在5到10秒内,图像开始加载,开始时模糊不清,但很快就变得清晰。大约7秒钟左右,它与最终版本几乎没有区别。
在10秒标记处,视口中的所有视觉内容均已加载完成。
在接下来的2秒内,异步JavaScript被加载和执行,运行所有非关键逻辑(分析、市场标识等)。
在最后的8秒内,其余的产品图片将被加载,以便用户滚动时可以查看它们。
当前浏览器的优先级
当前所有的浏览器引擎都执行不同的优先级排序策略,但这些都不是最优的。
Microsoft Edge和Internet Explorer 不支持优先级排序,因此所有内容都回到HTTP / 2默认值,该默认值是并行加载所有内容,并在所有内容之间平均分配带宽。Microsoft Edge将在未来的Windows版本中使用Chromium浏览器引擎,这将有助于改善这种情况。我们的示例页面说明了浏览器的大部分加载时间都被卡在了头部,因为图像减慢了阻塞脚本和样式表的传输速度。
这在视觉上造成了一种相当痛苦的体验:在大多数内容显示之前,人们需要盯着空白屏幕19秒,然后延迟1秒显示文本。观看加载进度动画时人们需要有耐心,因为在这19秒的空白屏幕上,你可能会觉得什么事都没有发生(尽管它确实发生了):
Safari 并行加载所有资源,并根据Safari认为的重要性来分配它们之间的带宽(脚本和样式表等渲染阻塞资源比图像更重要)。图像并行加载,但也与渲染阻塞内容同时加载。
尽管与Edge相似,所有内容都是同时下载的,但通过为渲染阻塞资源分配更多带宽,Safari可以更快地显示内容:
大约8秒时,样式表和脚本已经完成加载,因此页面可以开始显示。由于图像是并行加载的,所以它们也可以以部分状态呈现(渐进式图像是模糊的)。这个加载时间仍然是最优情况的两倍,但是比Edge要好得多。
在大约11秒钟时,字体已加载,因此可以显示出文本,并伴随有更多图像数据下载完成,因此图像会更清晰一些。这与最佳加载情况下大约在7秒标记点的体验相当。
在剩下的9秒内,随着下载的数据越来越多,图像会变得更清晰,直到最终在20秒内完成。
Firefox会构建一个资源分组依赖关系树,然后安排这些组一个接一个地加载,或者在组之间共享带宽。在给定组中,资源共享带宽并同时下载。图像被安排在渲染阻塞样式表之后加载,并且是并行加载。但是渲染阻塞脚本和样式表也并行加载,因而没有获得流水线化的好处。
在我们的示例中,由于图像被延迟到样式表完成之后,因此与Safari相比,这最终会带来更快的体验:
在6秒标记处,初始页面内容将与背景和模糊的产品图像一起呈现(相比之下,Safari需要8秒,最优情况下需要4秒)。
在8秒钟时,字体已加载,并且文本可以与稍微清晰的产品图像一起显示(相比之下,Safari为11秒,最优情况为7秒)。
在剩余的12秒内,随着剩余内容的加载,产品图像会变得更加清晰。
Chrome(以及所有基于Chrome的浏览器)将资源按优先级排列成一个列表。这对于按顺序加载的内容很有效,但是对于图像就不那么有效了。在开始下一个图像前,每个图像都需要100%完成加载。
实际上,这几乎与最佳加载情况一样好,唯一的区别是图像一次加载一个,而不是并行加载:
在5秒之前,Chrome的体验与最佳情况相同,背景显示时间点为4秒,文本内容显示时间点为5秒。
在接下来的5秒内,可视图像一次加载一个,直到它们在10秒内全部完成(相比之下,在最佳情况下,它们在7秒时只是稍微模糊,而在剩下3秒内会变清晰)。
页面的可视部分在10秒(与最佳情况相同)完成之后,剩下的10秒用于运行异步脚本并加载隐藏的图像(与最佳情况一样)。
视觉比较
从视觉上看,即使这些浏览器从技术上加载所有内容花费的时间相同,影响也可能非常巨大:
服务器端优先级
客户端(浏览器)请求HTTP/2优先级,服务器则根据请求决定如何处理。很多服务器根本不支持使用优先级来做任何事情,但是对于那些支持优先级的服务器,它们都尊重客户端的请求。另一种选择是,考虑到客户端的请求,决定在服务器端使用的最佳优先级。
根据规范,HTTP/2优先级是一个依赖树,它需要对所有正在运行的请求有充分的了解,以便能够对彼此进行资源的优先级划分。这允许非常复杂的策略,但是很难在浏览器或服务器端得到很好地实现(从不同的浏览器策略和不同级别的服务器支持可以看出)。为了使优先排序更容易管理,我们开发了一个更简单的优先排序方案,它仍然具有优化调度所需的所有灵活性。
Cloudflare优先级划分方案包含64个优先“级别”,每个优先级中都有若干资源组,这些资源组决定它们之间如何共享连接:
在继续进入下一个较低优先级之前,将转移较高优先级的所有资源。
在给定的优先级内,有3个不同的“并发”组:
0:并发“0”组中的所有资源都按请求的顺序发送,使用100%的带宽。只有在下载了所有并发“0”组资源之后,才会考虑相同级别的其他组。
**1:**并发“1”组中的所有资源按请求顺序发送。可用带宽平均分配给并发“1”组和并发“n”组。
N:并发“n”组中的资源是并行发送的,从而在它们之间分配可用于该组的带宽。
实际上,并发“0”组对于需要顺序处理的关键内容(脚本、CSS等)非常有用。并发“1”组对于不太重要的内容非常有用,这些内容可以与其他资源共享带宽,但是资源本身仍然可以从顺序处理中获益(异步脚本、非渐进式图像等)。并发“n”组对于从并行处理(渐进图像、视频、音频等)中获益的资源非常有用。
Cloudflare默认优先级
启用后,增强的优先级排序将实现上述资源的“最佳”调度。应用的具体优先级如下所示:
这种优先级方案允许按顺序发送渲染阻塞的内容,然后并行发送可见图像,然后在一定程度上共享页面内容的其余部分,以平衡应用程序和内容加载。需要注意的是,并不是所有的浏览器都能区分不同类型的样式表和脚本,但是在所有情况下这一方案都能显著提高速度。默认情况下速度会快50%,尤其是对于Edge和Safari用户来说:
使用Workers自定义优先级
默认设置更快固然很好,但真正有趣的是,Cloudflare Workers也具有配置优先级的能力,因此站点可以覆盖资源的默认优先级或实现其自己的完整优先级方案。
如果一个Worker向响应添加“cf-priority”请求头,则Cloudflare边缘服务器将对该响应使用指定的优先级和并发性。请求头的格式是/,类似于response.headers.set('cf-priority', “30/0”);意思是将给定响应的优先级设置为30,并发性为0。同理,“30/1”会将并发设置为1,“30/n”会将并发设置为n。
有了这种程度的灵活性,站点可以调整资源优先级以满足其需求。例如,提高一些关键异步脚本的优先级,或者在浏览器识别出在视窗的英雄图像之前,提高它们的优先级。
为了帮助通知任意优先级决定,Workers还将公布(传递给Worker的fetch事件监听器的)请求对象中浏览器请求的优先级信息。传入的请求优先级是一个分号分隔的属性列表,看起来像这样:“weight=192;exclusive=0;group=3;group-weight=127”.
weight:浏览器请求的HTTP / 2优先级权重。
exclusive:浏览器请求的HTTP / 2独占标志(对于基于Chromium的浏览器,这个数字为1;对于其他浏览器,这个数字为0)。
group:请求组的HTTP / 2流ID(Firefox只有非零流ID)。
group-weight:请求组的HTTP/2权重(Firefox只有非零权重)。
这仅仅是个开端
调整和控制响应优先级的能力是未来许多工作将从中受益的基本构件。我们将在它的基础上实现我们自己的高级优化。而通过在Workers中展示它,我们也向站点和研究人员开放了它,以试验不同的优先级策略。通过应用程序市场,公司也可以在Workers平台上构建新的优化服务,并将其提供给其他站点使用。
如果您使用的是Pro计划或更高版本,请转到Cloudflare信息中心中的“速度”标签,然后启用“增强的HTTP / 2优先级”以加速您的网站。