HTTP/2 初探

HTTP/2 是一个比它的几个前任更快、更简单、更稳定的 HTTP 协议。在 HTTP/2 中,我们可以摒弃掉很多以往针对 HTTP/1.1 想出来的“歪招儿”,因为它们的解决方案都内置在了传输层中。不仅如此,它还为我们进一步优化应用和提升性能提供全新的机会!

从 SPDY 到 HTTP/2

SPDY 是 Google 在 2009 年发布的一个实验性协议,它是一个应用层的网络传输协议,也是 HTTP/2 的前身。SPDY 并不是为替代 HTTP 而生,它的目的是通过多路复用、请求优化和 HTTP 头部压缩等功能,来最小化 HTTP/1.1 的各种性能限制所导致的延迟。具体来说,这个项目设定的目标如下:

  • 页面加载时间 (PLT) 减少 50%。
  • 无需网站作者修改任何内容。
  • 将部署复杂性降至最低,无需变更网络基础设施。
  • 与开源社区合作开发这个新协议。
  • 收集真实性能数据,验证这个实验性协议是否有效。

首次发布后不久,Google 分享了 SPDY 协议的实现结果、文档和源代码,宣布在实验室条件下取得了 55% 的速度提升。

几年后的 2012 年,这个新的实验性协议得到了 Chrome、Firefox 和 Opera 的支持,越来越多的网站开始在部署 SPDY。事实上,在行业中被越来越多地采用之后,SPDY 已经具备了成为一个标准的条件。

观察到这一趋势后,HTTP 工作组将这一工作提上议事日程,吸取 SPDY 的经验教训,并在此基础上制定了官方“HTTP/2”标准。在拟定草案、向社会征集 HTTP/2 建议并经过内部讨论之后,HTTP 工作组决定将 SPDY 规范作为新 HTTP/2 协议的基础。

在接下来几年中,SPDY 和 HTTP/2 继续共同演化,SPDY 作为实验性分支,为 HTTP/2 标准测试新功能和建议,对要包含到 HTTP/2 标准中的每条建议进行测试和评估。最终,这个过程持续了三年,期间产生了十余个中间草案。

最终,在2015 年初,IETF 审阅了新的 HTTP/2 标准并批准发布。SPDY 与 HTTP/2 的共同演化使得它在诞生之日就已经是一个经过大量实践的标准,发布后不久,就得到了广泛应用。

为何选择 HTTP/2

HTTP/1.x 的问题

想了解 HTTP/2 的优势,就得从 HTTP/1.x 存在的问题说起。

HTTP/1.x 中存在许多问题使得它越来越难以满足快速发展的互联网。

其中,HTTP/1.x 最大的问题就是一个连接同时只能处理一个请求。这意味着同一个连接下发起的多个请求只能逐个请求和接收。同时,由于没有优先级管理,HTTP/1.x 同一个连接中的多个请求只能采取 FIFO 的方式,依次完成。如果连接被某个耗时长的请求占用,那么其它所有请求就只能排队等候,直到收到回复或者超时。这就是所谓的队头阻塞(Head-of-Line Blocking,HOLB)。

HTTP/1.1 without pipelining

这个问题的一个解决方案就是 HTTP pipelining。通过 HTTP pipelining,在同一个连接上,可以不等队伍前方的请求收到响应,就先行发送后续的请求。这样一来请求就不会有延迟,使得服务器可以提早开始处理请求,一些耗时长的,如涉及数据库查询的请求,就可以提早进行,理论上可以提高 HTTP 的效率。

HTTP/1.1 with pipelining

然而,响应还是需要逐个排队接收,所以它并不是真正的多路复用,但至少是个不错的提升了(如果它按照理论运行的话)。在 pipelining 开启后,HOLB 依旧存在,因为 response 仍然是逐个接收的。甚至和之前比起来,使用 pipelining 后,HOLB 更明显了。此外,由于 pipelining 实现起来复杂度比较高,在实践中还常常出现各种错误,所以它并没有被广泛推广。事实上,大多数主流的浏览器都禁用了 pipelining。

另一个解决方案就是对同一个 host 同时开启多个 HTTP 连接,这样就可以并行请求和接收,能够更快地获取到多个资源。但这仍然存在一些问题,例如建立连接产生的资源损耗等。并且同个域名存在最大连接数,所以为了提高连接数,一些网站只好将网页上的资源部署到不同的域名下。除此之外,由于 TCP 的拥塞控制使得 TCP 连接在建立后有一个缓启动的过程,所以多连接的方案实际上贡献的性能提升并没有想象中那么多。

此外,HTTP/1.x 还存在文本协议开销大、缺乏首部压缩等问题。综合这些来看,HTTP/1.x 只意味着更高的系统需求,和更低的性能表现。

HTTP/2 的改进

二进制分帧层

HTTP/2 所有性能增强的核心在于新的二进制分帧层,它定义了如何封装 HTTP 消息并在客户端与服务器之间传输。二进制分帧层指的是位于传输层与应用层的高级 HTTP API 之间一个新编码机制。HTTP 的语义(包括各种动词、方法、标头)都不受影响,不同的是传输期间对它们的编码方式变了。HTTP/1.x 协议以换行符作为纯文本的分隔符,而 HTTP/2 将所有传输的信息分割为更小的消息和帧,并采用二进制格式对它们编码。其中 HTTP1.x 的首部信息会被封装到 HEADER frame,而相应的 Request Body 则封装到 DATA frame 里面。

二进制分帧层

简言之,HTTP/2 将 HTTP 协议通信分解为二进制编码帧的交换,这些帧对应着特定数据流中的消息。所有这些都在一个 TCP 连接内复用。这是 HTTP/2 协议所有其他功能和性能优化的基础。

由于编码方式的不同,HTTP/2 无法向下兼容,这也是 HTTP/2 的版本号是 2 而不是 1.2 的主要原因。

连接复用

在 HTTP/1.x 中,如果客户端要想发起多个并行请求以提升性能,则必须使用多个 TCP 连接。但 HTTP/2 中新的二进制分帧层突破了这些限制,实现了完整的请求和响应复用:客户端和服务器可以将 HTTP 消息分解为互不依赖的帧,然后交错发送,最后再在另一端把它们重新组装起来。这是 HTTP 2 最重要的一项增强。

14740804026266

HTTP/2 中的新二进制分帧层解决了 HTTP/1.x 中存在的队首阻塞问题,也消除了并行处理和发送请求及响应时对多个连接的依赖。最终使得应用速度更快、开发更简单、部署成本更低。

值得注意的是,HTTP/2 解决了 HTTP 的 HOL 阻塞,但并没有解决 TCP 上的 HOL 阻塞。

数据流优先级

将 HTTP 消息分解为很多独立的帧之后,客户端和服务器交错传输这些帧的顺序,就成为关键的性能决定因素。为了做到这一点,HTTP/2 允许每个数据流都有一个优先级和依赖关系。数据流依赖关系和权重的组合使得服务器可以使用此信息控制 CPU、内存和其他资源的分配,确保将高优先级响应以最优方式传输至客户端。

数据流依赖关系和权重表示传输优先级,而不是要求,因此不能保证特定的处理或传输顺序。即,客户端无法强制服务器通过数据流优先级以特定顺序处理数据流。

单一连接

有了新的分帧机制后,HTTP/2 不再依赖多个 TCP 连接去并行复用数据流;每个数据流都拆分成很多帧,而这些帧可以交错,还可以分别设定优先级。因此,所有 HTTP/2 连接都是持久的,而且每个来源仅需要一个连接,随之带来诸多性能优势。

大多数 HTTP 传输都是短暂且急促的,而 TCP 则针对长时间的批量数据传输进行了优化。 通过重用相同的连接,HTTP/2 既可以更有效地利用每个 TCP 连接,也可以显著降低整体协议开销。不仅如此,使用更少的连接还可以减少占用的内存和处理空间。这降低了整体运行成本并提高了网络利用率和容量。

标头压缩

每个 HTTP 传输都承载一组标头,这些标头说明了传输的资源及其属性。 在 HTTP/1.x 中,标头中的数据始终以纯文本形式发送,通常会给每个传输增加 500–800 字节的开销。如果使用 HTTP Cookie,增加的开销有时会达到上千字节。为了减少此开销和提升性能,HTTP/2 使用 HPACK 压缩格式压缩请求和响应中的标头数据,这种格式采用两种简单但是强大的技术:

  • 这种格式支持通过静态 Huffman 编码对传输的标头字段进行编码,从而减小了各个传输的大小。
  • 这种格式要求客户端和服务器同时维护和更新一个包含之前见过的标头字段的索引列表,此列表随后会用作参考,对之前传输的值进行有效编码。

作为一种进一步优化方式,HPACK 压缩上下文包含一个静态表和一个动态表:静态表在规范中定义,并提供了一个包含所有连接都可能使用的常用 HTTP 标头字段的列表;动态表最初为空,将根据在特定连接内改变的值进行更新。

HPACK 标头压缩

流控制

流控制是一种阻止发送方向接收方发送大量数据的机制。这很容易让人联想到 TCP 流控制,它们所要解决的问题很相似。不过,由于 HTTP/2 数据流在一个 TCP 连接内复用,TCP 流控制既不够精细,也无法提供必要的应用级 API 来调节各个数据流的传输。为了解决这一问题,HTTP/2 提供了一组简单的构建块,允许客户端和服务器实现其自己的流控制。

例如,HTTP2 的流控制允许浏览器仅提取一部分特定资源,通过将流控制窗口减小为零来暂停提取,稍后再行恢复。换句话说,它允许浏览器提取图像预览,进行显示并允许其他高优先级数据流继续传输,然后在更关键的资源完成加载后恢复提取。

服务端推送

HTTP/2 新增的另一个强大的新功能是,服务器可以对一个客户端请求发送多个响应。 换句话说,除了对最初请求的响应外,服务器还可以向客户端推送额外资源,而无需客户端明确地请求。之所以要提供这个服务,是因为一个文档被请求回来时,往往还需要再次请求很多文档内的其他资源,如果这些资源的请求不用客户端发起,而是服务端提前预判发给客户端,那么就会减少大量时延。

HTTP2 协议也没有规定服务器端到底该怎样推送这个资源。服务端可以自己制定不同的策略,可以是根据客户端明确写出的推送请求;或者是服务端通过学习得来;再或者是通过额外的HTTP首部想服务端表明意向。

这个服务的特点是:

  • 只有建立连接后,服务器才可以推送资源(发送 PUSH_PROMISE 帧),也就是说服务器不能无缘无故的主动向客户端推送资源。
  • 客户端可以发送 RST_STREAM 拒绝服务器推送来的资源。
  • 推送的资源可以由不同页面共享
  • 服务器可以按照优先级来推送资源

iOS 对 HTTP/2 的支持

在 iOS 上,NSURLSession 提供了对 HTTP/2 的支持。只要服务器支持 HTTP/2,系统就会自动使用它,否则将自动选择 HTTP/1.1 或其它可用协议。

需要注意的是,iOS 只支持加密连接的 HTTP/2 协议,HTTP/2 服务器需要支持 ALPN 或者 NPN 加密连接。


移动客户端网络部分的不少初步优化还比较依赖于 HTTP/2 的推进。MTHawkeye 中已经加入了对 HTTP/2 的检测,大家平时在开发的过程中,可以关注下,是否有可能将现存的 HTTP/1.x 升级到 HTTP/2。

延伸阅读