2

HTTP/1 已经支撑我们走到今天,但是现代 Web 应用的需求迫使我们关注其设计缺陷。下面是它面临的一些比较重要的问题;这自然也是设计 HTTP/2 要解决的核心问题。

并没有“HTTP/1”这种专业术语;此处这一用法(还有 h1),是对 HTTP/1.0 (RFC 1945)和 HTTP/1.1(RFC 2616)的简称。

队头阻塞

浏览器很少只从一个域名获取一份资源。大多数时候,它希望能同时获取许多资源。设想这样一个网站,它把所有图片放在单个特定域名下。HTTP/1 并未提供机制来同时请求这些资源。如果仅仅使用一个连接,它需要发起请求、等待响应,之后才能发起下一个请求。

h1 有个特性叫 管道化(pipelining),允许一次发送一组连续的请求,而不用等待应答返回。这样可以避免连接延迟。但是该特性只能按照发送顺序依次接收响应。而且,管道化备受互操作性和部署的各种问题的困扰,基本没有实用价值。

在请求应答过程中,如果出现任何状况,剩下所有的工作都会被阻塞在那次请求应答之后。这就是 队头阻塞 (Head-of-line blocking或缩写为HOL blocking),它会阻碍网络传输和 Web 页面渲染,直至失去响应。

为了防止这种问题,现代浏览器会针对单个域名开启 6 个连接,通过各个连接分别发送请求。它实现了某种程度上的并行,但是每个连接仍会受到 队头阻塞 的影响。另外,这也没有高效利用有限的设备资源。

低效的TCP利用

传输控制协议(TCP) 的设计思路是:对假设情况很保守,并能够公平对待同一网络的不同流量的应用。它的避免拥塞机制被设计成即使在最差的网络状况下仍能起作用,并且如果有需求冲突也保证相对公平。这是它取得成功的原因之一

它的成功并不是因为传输数据最快,而是因为它是最可靠的协议之一,涉及的核心概念就是 拥塞窗口(congestion window) 。拥塞窗口是指,在接收方确认数据包之前,发送方可以发出的 TCP 包的数量。 例如,如果拥塞窗口指定为 1,那么发送方发出 1 个数据包之后,只有接收方确认了那个包,才能发送下一个。

一般来讲,每次发送一个数据包并不是非常低效。TCP 有个概念叫 慢启动(Slow Start), 它用来探索当前连接对应拥塞窗口的合适大小。慢启动的设计目标是为了让新连接搞清楚当前网络状况,避免给已经拥堵的网络继续添乱。它允许发送者在收到每个确认回复后额外发送 1 个未确认包。这意味着新连接在收到 1 个确认回复之后,可以发送 2 个数据包; 在收到 2 个确认回复之后,可以发 4 个;以此类推。这种几何级数增长很快就会到达协议规定的发包数上限,这时候连接将进入拥塞避免阶段,

image

这种机制需要几次往返数据请求才能得知最佳拥塞窗口大小。但在解决性能问题时,就这 区区几次数据往返也是非常宝贵的时间(成本)。现代操作系统一般会取 4~10 个数据包作为初始拥塞窗口大小。如果你把一个数据包设置为最大值下限 1460 字节(也就是 最大有效负载),那么只能先发送 5840 字节(假定拥塞窗口为 4),然后就需要等待接收确认回复。

如今的 Web 页面平均大小约 2MB,包括 HTML 和所有依赖的资源。在理想情况下, 这需要大约 9 次往返请求来传输完整个页面。除此之外,浏览器一般会针对同一个域名开启 6 个并发连接,每个连接都免不了拥塞窗口调节。

上面提到的那些数字是怎么得出来的?这个时候了解一些数学知识很有必要,有助于估算对网络传输的影响,看看到底是增加还是减少了传输字节数。假设拥塞窗口的大 小每次往返请求之后会翻一番,每个数据包承载 1460 字节。在理想情况下,呈现出等比数列。

image

直到第 9 次,2MB 数据才能全部发完。不过,这过度简化了现实情况。在窗口大小达 到 1024 个数据包时,要么会触发一个叫 ssthresh(慢启动阈值) 的上限值,要么窗口 会自动缩小;不管哪种情况,几何级数增长都会终止。不过,用于粗略估算时,这种简单方法已经够用了。

前面提到过,因为 h1 并不支持多路复用,所以浏览器一般会针对指定域名开启 6 个并发 连接。这意味着拥塞窗口波动也会并行发生 6 次。TCP 协议保证那些连接都能正常工作, 但是不能保证它们的性能是最优的

臃肿的消息首部

虽然 h1 提供了压缩被请求内容的机制,但是消息首部却无法压缩。消息首部可不能忽略, 尽管它比响应资源小很多,但它可能占据请求的绝大部分(有时候可能是全部)。如果算 上 cookie,有个几千字节就很正常了。

据 HTTP 历史存档记录,2016 年末,请求首部一般集中在 460 字节左右。对于包含 140 个 资源的普通Web 页面,意味着它在发起的所有请求中大约占 63KB。

想想之前关于 TCP 拥塞窗口管理的讨论,发送该页面相关的所有请求可能需要 3~4 轮往返,因此网络延迟的损耗会被迅速放大。此外,上行带宽通常会受到网络限制,尤其是在移动网络环境中,于是拥塞窗口机制根本来不及起作用,导致更多的请求和响应。

消息首部压缩的缺失也容易导致客户端到达带宽上限,对于低带宽或高拥堵的链路尤其如此。体育馆效应(Stadium Effect) 就是一个经典例子。如果成千上万人同一时间出现在同一地点(例如重大体育赛事),会迅速耗尽无线蜂窝网络带宽。这时候,如果能压缩请求首部,把请求变得更小,就能够缓解带宽压力,降低系统的总负载。

受限的优先级设置

如果浏览器针对指定域名开启了多个 socket(每个都会受队头阻塞问题的困扰),开始请求资源,这时候浏览器能指定优先级的方式是有限的:要么发起请求,要么不发起。

然而 Web 页面上某些资源会比另一些更重要,这必然会加重资源的 排队效应。这是因为浏览器为了先请求优先级高的资源,会推迟请求其他资源。

但是优先级高的资源获取之后,在处理的过程中,浏览器并不会发起新的资源请求,所以服务器无法利用这段时间发送优先级低的资源,总的页面下载时间因此延长了。还会出现这样的情况:一个高优先级资源被浏览器发现,但是受制于浏览器处理的方式,它被排在了一个正在获取的低优先级资源之后。

第三方资源

虽然第三方资源不是 HTTP/1 特有的问题,但鉴于它日益增长的性能问题,我们也把它列在这里。

如今的 Web 页面上请求的很多资源完全独立于站点服务器的控制,我们称这些为 第三方资源。现代 Web 页面加载时长中往往有一半消耗在第三方资源上。虽然有很多技巧能把第三方资源对页面性能的影响降到最低,但是很多第三方资源都不在 Web 开发者的控制范围内,所以很可能其中有些资源的性能很差,会延迟甚至阻塞页面渲染。

任何关于 Web 性能的讨论,只要没有提到第三方资源引起的问题,都不算完整。(令人扫兴的是, h2 对此也束手无策。)

第三方资源究竟让页面慢多少? Akamai 的 Foundry 团队的研究显示,第三方资源的影响非常大,平均累计占到页面整体加载时间的一半 1。这份报告提出了新的用于跟踪第三方资源影响的指标,称为 3rd Party Trailing Ratio。它测量的是请求并展现第三方内容对页面渲染时间的影响程度。

参考


Pines_Cheng
6.5k 声望1.2k 粉丝

不挑食的程序员,关注前端四化建设。