上周我在斯德哥尔摩呆了几天,参加了一个HTTP研讨会,并参与了一些有趣的讨论。其中之一是关于HTTP推送及其优缺点,这是早期实验的结果。由于早期实验部署的糟糕结果,人们普遍对HTTP推送持怀疑态度,但我想分享我更乐观的看法。HTTP推送能做什么预加载不能做什么?从怀疑论者那里反复听到的观点是“与预加载相比,推送只节省了一个RTT(往返时间)”。实际上这并不总是正确的,有一个用例可以完成推送但不能预加载。利用服务器思考时间如今,HTML响应很少只是纯静态资源。它们通常是使用高级语言(可能稍微慢一些)通过从数据库中获取所需信息动态生成的。数百毫秒的响应时间并不常见,尽管可以而且应该优化后端响应时间。有一个常见的建议是尽早“刷新”HTML,在查询数据库和构建动态内容时发送第一块HTML。然而,并不是所有的服务器端架构都能如此简单地实现。另一个让事情变得困难的因素是,当我们需要开始向浏览器发送数据时,我们还不能确定响应的构建是否会完全成功。为了避免响应创建逻辑中的错误(例如,数据库错误或服务器端代码失败),我们需要在应用程序逻辑中创建一种方法来“回滚”已发送的响应数据并向用户显示错误消息。虽然这当然是可能的(甚至是自动的),但目前还没有通用的方法将其作为协议的一部分来执行。所以典型的场景是Web服务器等待后端建立页面数百毫秒,然后开始返回数据。此时我们遇到了慢启动(译者注:慢启动,参考这篇文章),所以第一个RTT只能发送大约14KB的数据,第二个大约28KB,以此类推。由此我们知道发送HTML需要服务器思考时间加上慢启动时间。在thinktime期间,浏览器对它接下来需要的资源一无所知,所以它不会发送任何对它接下来需要的资源的关键路径的请求。此外,即使我们尝试变得聪明并为这些资源添加预加载标头,如果我们不更早地刷新文档的开头,我们仍然没有使用思考时间。现在,将此与您可以使用HTTP推送执行的操作进行对比。服务器可以使用思考时间来推送相关的关键资源——尤其是CSS和JS。这样,当思考时间结束时,我们很可能已将所有关键资源推送到浏览器。作为一个额外的好处,这些资源还预热了TCP连接并增加了拥塞窗口,确保在思考时间之后的第一个RTT中,可以使用28KB、56KB甚至更大的拥塞窗口发送HTML(这取决于思考需要多长时间,以及我们在那段时间投入了多少资源)。我们来看一个具体的案例:一个120KB的HTML页面,关键CSS是24KB,关键JS是74KB。在100msRTT、无限带宽的网络环境下如何加载?没有HTTP推送,生成HTML等待300ms,然后4个RTT发送HTML,因为启动慢,1个RTT请求JS和CSS。在第一次渲染之前需要800多毫秒。无推送的页面加载使用HTTP推送,HTML请求一到达就推送CSS和JS,发送它们需要3RTT(同样是由于启动慢),它们将拥塞窗口增加到128KB左右,将在发送HTML时,一个RTT就足够了。首次渲染总时间:400毫秒。使用HTTP推送首次呈现的页面加载速度提高50%!一点也不差。..我认为人们错误地使用HTTP推送的原因之一是他们在某些情况下使用它不会提供任何好处甚至会损害效率。盲目推送静态资源HTTP推送可能做错的事情之一就是告诉自己,“啊,所有页面都需要这些资源,将它们配置为由所有页面推送”。这很糟糕,原因是缓存。访问第一个页面后,这些资源很可能在用户的浏览器缓存中,但你在推送。您可能会争辩说这比内联所有这些资源要好得多。好吧,那很好,但是,我必须反过来告诉你,内联资源也是一个坏主意。因此,如果您以这种方式盲目地推送资源,请确保它是您希望在页面中内联的唯一资源,这是关键的CSS。否则,您可能会降低重复请求的速度。您可能认为流重置将有助于推送缓存资源以避免浪费带宽和时间。你可能错了。显然,并非所有浏览器都会检查缓存并停止推送缓存资源。即使他们这样做了,在流重置信号到达服务器之前发送数据仍然需要一个完整的RTT。尤其是当有多个资源时,这样做可能会造成大量的数据浪费。将内容放入浏览器缓存您可能认为推送会将资源放入浏览器缓存,这可用于执行诸如使当前资源无效之类的操作。至少现在不是。研讨会讨论的主题之一是我们可能需要更改当前的推送行为以支持与浏览器缓存的直接交互。但目前,push还不能做到这一点。推送响应进入推送缓存,并且仅在实际请求时才放入HTTP缓存中。因此,如果您正在推送资源并希望它们在未来的页面上使用,那么浏览器有可能在使用它们之前将它们从推送缓存中丢弃。至少这是目前实施的方式。在HTML发布后填充管道通常在页面下载循环期间使用的带宽存在间隙。这意味着我们没有尽快交付资源,通常是因为浏览器发现资源的延迟。尽管我们应该尝试传递页面所需的资源来填补这些空白,但通常使用预加载比使用推送更好。预加载将缓存、cookie和内容协商考虑在内,并且不会像推送那样带来过度发送或错误发送的风险。在填补这些空白方面,push没有优势,只有劣势。所以最好不要使用push来达到这个目的,而是使用preloading。CacheDigests从上面我们可以看出HTTP推送的一大缺点就是服务器不一定知道浏览器的缓存状态,所以我们在推送的时候可能会推送缓存中已经存在的资源。有一个名为CacheDigests的标准扩展的提案。基本思路是浏览器在初始化HTTP/2连接后向服务器发送一个摘要,服务器在发送资源前可以准确判断该资源是否已经存在于浏览器缓存中。该提案仍处于早期阶段,可能需要进行简化以降低实施成本,但我敢说,如果没有此功能,HTTP推送只是一个半成品。总结HTTP推送可用于显着提高加载性能。如果使用得当,它可以加速第一个关键路径加载,从而提高性能指标。推送仍然是一项非常新的技术,并且像所有新工具一样,在找到使用它的最佳方式之前还有很长的路要走。这段旅程会有些痛苦。因此,早期实验的初步结果可能并不像我们希望的那样。让我们将这些结果视为我们对推送的使用需要更多独创性的标志,不要草率得出结论认为它是一个无用的功能。感谢TimKadlec和MarcosCaceres审阅本文(特别感谢Tim,感谢他帮助制作RTT图原型)。
