在过去一年左右,性能优势的开发人员的景观发生了重大变化,HTTP / 2的出现也许是最重要的。不再是HTTP / 2的一个功能我们松了。已经到了,随之而来的是服务器推送!
除了解决常见的HTTP / 1性能问题(例如,线路阻塞和未压缩头部的头)外,HTTP / 2还给我们服务器推送!服务器推送允许您在网站资源甚至要求之前发送站点资源。实现HTTP / 1优化实践(如内联)的性能优势是一种优雅的方式,但没有这种做法的缺点。
在本文中,您将了解有关服务器推送的所有信息,从它的工作原理到解决的问题。您还将学习如何使用它,如何判断它是否正常工作以及它对性能的影响。让我们开始!
什么是服务器推送?
访问网站一直遵循请求和响应模式。用户向远程服务器发送请求,并且一些延迟,服务器响应请求的内容。
对Web服务器的初始请求通常用于HTML文档。在这种情况下,服务器应答请求的HTML资源。然后,HTML被浏览器解析,其中发现了对其他资产的引用,例如样式表,脚本和图像。发现后,浏览器会为这些资产分别提出请求,然后对其进行实物回应。
这一机制的问题是,它迫使用户等待浏览器来查找和检索关键资产,直到经过 HTML文档已被下载。这会延迟渲染并增加加载时间。
通过服务器推送,我们可以解决这个问题。服务器推送让服务器抢先将网站资产“推送”到客户端,而用户没有明确要求他们。小心使用时,我们可以发送我们知道用户需要的页面。
假设您有一个网站,其中所有页面都依赖于在外部样式表中定义的样式styles.css
。当用户index.html
从服务器请求时,我们可以styles.css
在我们开始发送响应之后推送给用户index.html
。
而不是等待服务器发送index.html
和然后等待浏览器请求和接收styles.css
,用户只需等待服务器与响应都 index.html
和styles.css
初始请求。这意味着浏览器可以开始渲染页面,而不必等待。
可以想像,这可以减少页面的渲染时间。它还解决了一些其他问题,特别是在前端开发工作流程中。
服务器推送解决什么问题?链接
减少重要内容到服务器的往返是服务器推送解决的问题之一,但并不是唯一的问题。服务器推送作为许多HTTP / 1特定优化反模式的合适替代方法,例如将CSS和JavaScript直接内置到HTML中,以及使用数据URI方案将二进制数据嵌入到CSS和HTML中。
这些技术发现在HTTP / 1优化工作流程中进行购买,因为它们减少了一个页面的“感知呈现时间”,这意味着当页面的整体加载时间可能不会减少时,该页面似乎加载速度更快用户。毕竟这是有道理的。如果将CSS内嵌到<style>
标签中的HTML文档中,浏览器可以开始将样式应用于HTML,而无需等待从外部来源获取它们。这个概念适用于使用数据URI方案的内联脚本和内联二进制数据。
似乎是一个很好的方法来解决问题,对吧?当然 – 对于HTTP / 1工作流程,您无需其他选择。然而,当我们这样做时,我们吞下的毒丸是内联内容无法高效缓存。当像样式表或JavaScript文件的资源保持外部和模块化时,可以高效地缓存。当用户导航到需要该资产的后续页面时,可以从缓存中提取该页面,从而无需对服务器进行其他请求。
然而,当我们内嵌内容时,该内容没有自己的缓存上下文。它的缓存上下文与其内联资源相同。例如,以内联CSS为例。如果HTML文档的缓存策略始终从服务器获取标记的新副本,则内联CSS将不会自动缓存。当然,它的一部分的文档可能被缓存,但包含这个重复的CSS的后续页面将被重复下载。即使缓存策略较为宽松,HTML文档的保质期也会有限。这是我们愿意在HTTP / 1优化工作流程中进行的权衡。它的工作,对于首次访客来说是非常有效的。第一印象通常是最重要的。
这些是服务器推送地址的问题。当您推送资产时,您将获得内联实现的实际利益,但您也可以将资产保留在保留自己的缓存策略的外部文件中。这一点有一个值得注意的地方,但是这篇文章已经结束了。现在,让我们继续吧。
我已经谈到了为什么你应该考虑使用服务器推送,以及为用户和开发人员修复的问题。现在,让我们来谈谈如何它的使用。
如何使用服务器推送
使用服务器推送通常涉及使用Link
HTTP头,采用以下格式:
Link: </css/styles.css>; rel=preload; as=style
请注意,我通常说。你上面看到的是实际的preload
资源提示。这是来自服务器推送的独立和不同的优化,但是大多数(并非所有)HTTP / 2实现将推Link
送包含preload
资源提示的标头中指定的资产。如果服务器或客户端选择不接受推送的资源,客户端仍然可以为所指示的资源启动早期提取。
as=style
标题的部分不是可选的。它通知浏览器推送资产的内容类型。在这种情况下,我们使用一个值style
来表示推送的资产是样式表。您可以指定其他内容类型。请注意,省略该as
值可能导致浏览器下载推送的资源两次。所以别忘了!
现在您知道如何触发推送事件,我们如何设置Link
标题?你可以通过两条路线:
- 您的Web服务器配置(例如Apache
httpd.conf
或.htaccess
); - 后端语言功能(例如,PHP的
header
功能)。
设置LINK
您的服务器配置的标题
以下是一个示例:在请求HTML文件时,配置Apache(via httpd.conf
或.htaccess
)来推送样式表:
<FilesMatch "\.html$">
Header set Link "</css/styles.css>; rel=preload; as=style"
<FilesMatch>
在这里,我们使用该FilesMatch
指令来匹配结尾的文件的请求.html
。当符合此条件的请求时,我们向Link
响应中添加一个标头,该响应告诉服务器将资源推送到/css/styles.css
。
旁注: Apache的HTTP / 2模块还可以使用该H2PushResource
指令启动资源的推送。该指令的文档指出,如果使用Link
头文件方法,此方法可以提前启动。根据您的具体设置,您可能无法访问此功能。本文后面显示的性能测试使用Link
头文件方法。
到目前为止,Nginx不支持HTTP / 2服务器推送,到目前为止,软件的更新日志中没有指出已经添加了对它的支持。这可能会随着Nginx的HTTP / 2实现成熟而改变。
LINK
在后端代码设置标题
设置Link
标题的另一种方法是通过服务器端语言。当您无法更改或覆盖Web服务器的配置时,这很有用。下面是一个使用PHP header
函数设置Link
头文件的例子:
header("Link: </css/styles.css>; rel=preload; as=style");
如果您的应用程序驻留在共享托管环境中,那么修改服务器的配置不是一个选项,那么这种方法可能就是您需要继续的。您应该能够以任何服务器端语言设置此标题。在开始发送响应正文之前,请务必这样做,以避免潜在的运行时错误。
推出多个资产
到目前为止,我们所有的例子只是说明如何推动一项资产。如果你想推多个怎么办?这样做会有意义吗?毕竟,网页由不仅仅是样式表组成。以下是推送多项资产的方法:
Link: </css/styles.css>; rel=preload; as=style, </js/scripts.js>; rel=preload; as=script, </img/logo.png>; rel=preload; as=image
当您要推送多个资源时,只需用逗号分隔每个推式指令。由于资源提示是通过Link
标签添加的,所以这种语法是如何将其他资源提示与您的推送指令混合使用。以下是将push指令与preconnect
资源提示进行混合的示例:
Link: </css/styles.css>; rel=preload; as=style, <https://fonts.gstatic.com>; rel=preconnect
多个Link
标题也是有效的。以下是如何配置Apache Link
为HTML文档的请求设置多个标题:
<FilesMatch "\.html$">
Header add Link "</css/styles.css>; rel=preload; as=style"
Header add Link "</js/scripts.js>; rel=preload; as=script"
<FilesMatch>
这种语法比将一串逗号分隔的值串起来更方便,它的工作原理相同。唯一的缺点是它并不那么紧凑,但是便利性是通过电线发送的几个额外的字节。
现在你知道如何推动资产,让我们看看如何判断它是否正常工作。
如何判断是否服务器推送工作
所以,你添加了Link
头来告诉服务器推一些东西。剩下的问题是,你怎么知道它是否还能工作?
这取决于浏览器。最新版本的Chrome将在开发人员工具中的网络实用程序的启动器列中显示一个被推送的资产。
此外,如果我们将资源悬停在网络请求瀑布中,我们将获得有关资产推送的详细时间信息:
识别推送资产时,Firefox不太明显。如果资产已被推送,其在开发人员工具中浏览器网络应用程序中的状态将显示灰点。
如果您正在寻找一种确定方式来判断资产是否被服务器推送,您可以使用nghttp
命令行客户端来检查HTTP / 2服务器的响应,如下所示:
nghttp -ans https://jeremywagner.me
此命令将显示事务涉及的资产的摘要。推送的资产在程序输出中将有一个星号,如下所示:
id responseEnd requestStart process code size request path
13 +50.28ms +1.07ms 49.21ms 200 3K /
2 +50.47ms * +42.10ms 8.37ms 200 2K /css/global.css
4 +50.56ms * +42.15ms 8.41ms 200 157 /css/fonts-loaded.css
6 +50.59ms * +42.16ms 8.43ms 200 279 /js/ga.js
8 +50.62ms * +42.17ms 8.44ms 200 243 /js/load-fonts.js
10 +74.29ms * +42.18ms 32.11ms 200 5K /img/global/jeremy.png
17 +87.17ms +50.65ms 36.51ms 200 668 /js/lazyload.js
15 +87.21ms +50.65ms 36.56ms 200 2K /img/global/book-1x.png
19 +87.23ms +50.65ms 36.58ms 200 138 /js/debounce.js
21 +87.25ms +50.65ms 36.60ms 200 240 /js/nav.js
23 +87.27ms +50.65ms 36.62ms 200 302 /js/attach-nav.js
在这里,我已经nghttp
在自己的网站上使用了(至少在撰写本文时)推送了五个资产。推送的资产在requestStart
列的左侧标有一个星号。
现在,我们可以确定何时推出资产,让我们看看服务器推送实际影响真实网站的性能。
测量服务器推送性能
衡量任何性能提升的效果需要一个很好的测试工具。Sitespeed.io是通过npm提供的优秀工具; 它自动执行页面测试并收集有价值的性能指标。选择适当的工具后,让我们快速了解测试方法。
测试方法
我想要以有意义的方式衡量服务器推送网站性能的影响。为了使结果有意义,我需要在六个不同的场景中建立比较点。这些情况分为两个方面:是否使用HTTP / 2或HTTP / 1。在HTTP / 2服务器上,我们要衡量服务器推送对多个指标的影响。在HTTP / 1服务器上,我们希望了解资产内联如何影响相同度量标准的性能,因为内联应该大致类似于服务器提供的优势。具体来说,这些情况如下:
- HTTP / 2无服务器推送
在这种状态下,网站运行在HTTP / 2协议上,但没有任何推送。网站运行“股票”,可以这么说。 - HTTP / 2只
推送CSS服务器推送,但仅适用于网站的CSS。网站的CSS相当小,使用了Brotli压缩应用程序,重点在2 KB 以上。 - 推动厨房水槽
所有在网站上的所有页面上使用的资产都被推送。这包括CSS,以及扩展到六个资产的1.4 KB JavaScript,以及5.9 KB的SVG图像分布在五个资产中。所有引用的文件大小再次应用在Brotli压缩之后。 - HTTP / 1,没有资产内
联网站在HTTP / 1上运行,没有任何资产被内联减少请求数量或提高渲染速度。 - CSS
仅CSS内联网站的CSS是内联的。 - 内置厨房水槽
网站上所有页面上使用的所有资产均为内联。CSS和脚本是内联的,但SVG图像是base64编码的,并直接嵌入到标记中。应该注意,base64编码的数据大约是其未编码的等价物的1.37倍。
在每种情况下,我通过以下命令启动了测试:
sitespeed.io -d 1 -m 1 -n 25 -c cable -b chrome -v https://jeremywagner.me
如果你想知道这个命令的内容,你可以查看文档。简单的说,这个命令测试我的网站的主页https://jeremywagner.me,具有以下条件:
- 页面中的链接不会被抓取。只有指定的页面被测试。
- 该页面被测试了25次。
- 使用“电缆状”网络调节配置文件。这转换为28毫秒的往返时间,下行速度为5,000千比特每秒,上行速度为1,000千比特每秒。
- 测试使用Google Chrome运行。
从每个测试中收集并绘制了三个指标:
- 第一次涂漆时间
这是页面首先可以在浏览器中看到的时间点。当我们努力使页面“感觉”好像正在快速加载时,这是我们尽可能减少的指标。 - DOMContentLoaded时间
这是HTML文档完全加载并已被解析的时间。同步JavaScript代码将阻止解析器,并导致此数字增加。使用标签async
上的属性<script>
可以帮助防止解析器阻止。 - 页面加载时间
这是页面及其资产完全加载所需的时间。
随着测试参数的确定,让我们看看结果!
测试成果
测试是在前面指定的六个场景中进行的,结果绘制在图表上。我们先来看看在每种情况下,第一次涂漆时间如何受到影响:
让我们先来谈谈一下如何设置图表。蓝色的图形部分表示平均第一次涂漆时间。橙色部分是第90百分位数。灰色部分表示最大涂装时间。
现在我们来谈谈我们看到的。最慢的情况是HTTP / 2和HTTP / 1驱动的网站,根本没有增强。我们确实看到,使用服务器推送CSS有助于将页面的平均速度提升约8%,而不是完全不使用服务器推送,甚至比在HTTP / 1服务器上嵌入CSS快5%。
然而,当我们推动所有可能的资产时,图片有所变化。第一次涂漆时间略有增加。在HTTP / 1工作流程中,我们可以将所有内容列入内容,我们可以实现与推送资产时相似的性能,尽管如此。
这里的裁决是清楚的:使用服务器推送,我们可以实现比我们可以在内联的HTTP / 1上实现的结果更好的结果。然而,当我们推动或内联许多资产时,我们观察到收益递减。
值得注意的是,使用服务器推送或内联功能优于首次访问者的增强功能。还值得注意的是,这些测试和实验正在一个小资产的网站上运行,所以这个测试用例可能不会反映出您的网站可实现的内容。
我们来看看每个场景对DOMContentLoaded时间的性能影响:
这里的趋势与上图中看到的趋势没有太大的不同,除了一个显着的偏离:在HTTP / 1连接上将实际资源列入内容的实例产生了非常低的DOMContentLoaded时间。这可能是因为内联减少了需要下载的资产数量,这使得解析器不间断地进行业务。
现在,我们来看看页面加载时间在每个场景中的影响:
早期测量的确定趋势通常也在这里持续存在。我发现只推出CSS实现了加载时间最大的好处。推送太多的资产可能会在某些情况下使网络服务器有点迟钝,但仍然比根本不推动任何东西更好。与内联相比,服务器推送产生的总体负载时间比内衬更好。
在我们结束本文之前,让我们来谈一谈你在服务器推送时应该注意的一些注意事项。
使用服务器推送注意事项
服务器推送不是您网站性能恶化的灵丹妙药。它有一些缺点,你需要认识到。
你可以推送太多的东西
在上述情景之一中,我正在推出大量资产,但是它们都占总体数据的一小部分。由于浏览器不仅需要下载HTML,而且还要下载所有其他资源,因此可能会拖延您的网页从绘画或交互的时间。你最好的选择是选择你所推的。样式表是开始的好地方(只要它们不是巨大的)。然后评估什么是有意义的推。
你可以推动不在页面
如果您有访问者分析来备份此策略,这不一定是坏事。一个很好的例子可能是一个多页面的注册表单,您可以在注册过程中为下一页推送资产。让我们清楚一点:如果你不知道你是否强迫用户抢先加载他们还没有看到的页面的资源,那么不要这样做。有些用户可能会受限制的数据计划,您可能会花费真正的资金。
配置您的HTTP / 2服务器正确
某些服务器为您提供了大量服务器推送相关配置选项。Apache mod_http2
有一些配置如何推送资产的选项。该H2PushPriority
设置应该是特别感兴趣的,尽管在我的服务器的情况下,我把它保留在默认设置。一些实验可以产生额外的性能优势。每个Web服务器都有一整套不同的开关和拨盘,供您进行实验,因此请阅读您的手册,并找出可用的内容!
推可能不被缓存
是否有服务器推送可能会损害性能,因为返回的访问者可能会再次将资产不必要地推送给他们。一些服务器尽力减轻这一点。Apache mod_http2
使用该H2PushDiarySize
设置来优化这一点。H2O Server具有称为缓存感知服务器推送的功能,该功能使用Cookie机制来记住推送的资产。
如果您不使用H2O服务器,您可以通过在没有Cookie的情况下推送资产,在Web服务器或服务器端代码上实现相同的操作。如果您有兴趣学习如何做到这一点,请查看我在CSS-Tricks上写的一篇文章。还值得一提的是,浏览器可以发送一个RST_STREAM
帧来向服务器发送不需要推送资产的信号。随着时间的推移,这种情况将更加优雅地处理。
可悲的是,我们一起接近我们时间的尽头。让我们来解决一下我们所学到的东西。
最后的想法
如果您已将您的网站迁移到HTTP / 2,那么您几乎没有任何理由不使用服务器推送。如果你有一个非常复杂的网站有很多资产,开始小。一个很好的经验法则是考虑推动你曾经舒适的任何内容。一个好的起点是推动您的网站的CSS。如果你之后感觉更有冒险性,那就考虑推动其他的东西。始终测试更改以了解它们如何影响性能。如果您足够修补,您可能会从此功能中获益。
如果您没有使用缓存感知服务器推送机制(如H2O Server),请考虑使用Cookie跟踪用户,并在没有Cookie的情况下仅将资产推送给他们。这将减少对已知用户的不必要的推送,同时提高未知用户的性能。这不仅对性能有好处,而且还表现出对用户的限制数据计划的尊重。