You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

11 KiB

HTTP/2.0

目录

  1. 思维导图
  2. 前言
  3. 设计和技术目标
    • 二进制分帧层
    • 流、消息与帧
    • 多项请求与响应
    • 请求优先级
    • 服务器推送
    • 首部压缩
  4. 二进制分帧层简介

前言

HTTP/2.0 的目的就是通过支持请求与响应的多路复用来减少延迟,通过压缩 HTTP 首部字段将协议开销降至最低,同时增加对请求优先级和服务器推送的支持。为达到这些目标,HTTP/2.0 还会给我们带来大量其他协议层面的辅助实现,比如新的流量控制、错误处理和更新机制。

HTTP/2.0 不会改动 HTTP 的语义,HTTP 方法、状态码、URI 及首部字段,等等这些核心概念一如往常。但是,HTTP/2.0 修改了格式化数据(分帧)的方式,以及客户端与服务器间传输这些数据的方式。这两点统帅全局,通过新的组帧机制向我们的应用隐藏了所有复杂性。换句话说,所有原来的应用都可以不必修改而在新协议运行。

设计和技术目标

HTTP/1.x 的设计初衷主要是实现要简单:HTTP/0.9 只用一行协议就启动了万维网;HTTP/1.0 则是对流行的 0.9 扩展的一个正式说明;HTTP/1.1 则是 IETF 的一份官方标准。因此,HTTP 0.9~1.x 只描述了现实是怎么一回事:HTTP 是应用最广泛、采用最多的一个互联网应用协议。

然而,实现简单是以牺牲应用性能为代价的,而这正是 HTTP/2.0 要致力于解决的:HTTP/2.0 通过支持首部字段压缩和在同一个连接上发送多个并发消息,让应用更有效地利用网络资源,减少感知的延迟时间。而且,它还支持服务器到客户端的主动推送机制。

二进制分帧层

HTTP/2.0 性能增强的核心,全在于新增的二进制分帧层,它定义了如何封装 HTTP 消息并在客户端与服务器之间传输。

这里所谓的层,指的是位于套接字接口与应用可见的高层 HTTP API 之间的一个新机制:HTTP 的语义,包括各种动词、方法、首部,都不受影响,不同的是传输期间对它们的编码方式变了。HTTP/1.x 以换行符作为纯文本的分隔符,而 HTTP/2.0 将所有传输的信息分割为更小的消息和帧,并对它们采用二进制格式的编码。

流、消息和帧

新的二进制分帧机制改变了客户端与服务器之间交互数据的格式,为了说明这个过程,我们需要了解 HTTP/2.0 两个新的概念:

  • 已建立的连接上的双向字节流。

  • 消息

    与逻辑消息对应的完整的一系列数据帧。

  • HTTP/2.0 通信的最小单位,每个帧包含帧首部,至少也会标识出当前帧所属的流。

所有 HTTP/2.0 通信都在一个连接上完成,这个连接可以承载任意数量的双向数据流。相应地,每个数据流以消息的形式发送,而消息由一个或多个帧组成,这些帧可以乱序发送,然后再根据每个帧首部的流标识符重新组装。

这简简单单的几句话里浓缩了大量的信息,要理解 HTTP/2.0,就必须理解流、消息和帧这几个基础概念:

  1. 所有通信都在一个 TCP 连接上完成
  2. 流是连接中的一个虚拟信道,可以承载双向的消息;每个流都有一个唯一的整数标识符(1、2 ~N)
  3. 消息是指逻辑上的 HTTP 消息,比如请求、响应等,由一或多个帧组成
  4. 帧是最小的通信单位,承载着特定类型的数据,如 HTTP 首部、负荷,等等

简言之,HTTP/2.0 把 HTTP 协议通信的基本单位缩小为一个一个的帧,这些帧对应着逻辑流中的消息。相应的,很多流可以并行的在同一个 TCP 连接上交换消息。

多向请求与响应

在 HTTP/1.x 中,如果客户端想发送多个并行的请求以及改进性能,那么必须使用多个 TCP 连接。这是 HTTP/1.x 交互模型的直接结果,该模型会保证每个连接每次只交互一个响应(多个响应必须排队)。更糟糕的是,这种模型也会导致队首阻塞,从而造成底层 TCP 连接的效率低下。

HTTP/2.0 中新的二进制分帧层突破了这些限制,实现了多向请求和响应:客户端和服务器可以把 HTTP 消息分解为互不依赖的帧,然后乱序发送,最后再在另一端把它们重新组合起来。

上图中包含了同一个连接上多个传输中的数据流:客户端正在向服务器传输一个 DATA 帧(stream 5),与此同时,服务器正向客户端乱序发送 stream 1 和 stream 3 的一系列帧。此时,一个连接上有 3 个请求/响应并行交换!

把 HTTP 消息分解为独立的帧,交错发送,然后在另一端重新组装是 HTTP/2.0 最重要的一项增强。事实上,这个机制会在整个 Web 技术栈中引发一系列连锁反应,从而带来巨大的性能提升,因为:

  • 可以并行交错的发送请求,请求之间互不影响
  • 可以并行交错的发送响应,响应之间互不干扰
  • 只使用一个连接即可并行发送多个请求和响应
  • 消除不必要的延迟,从而减少页面加载的时间
  • 不必再为绕过 HTTP/1.x 限制而多做很多工作

总之,HTTP/2.0 的二进制分帧机制解决了 HTTP/1.x 中存在的队首阻塞问题,也消除了并行处理和发送请求及响应时对多个连接的依赖。结果,就是应用速度更快,开发更简单,部署成本更低。

支持多向请求与响应,可以省掉针对 HTTP/1.x 限制所费的那些脑筋和工作,比如拼接文件、图片精灵、域名分区。类似的,通过减少 TCP 连接的数量,HTTP/2.0 也会减少客户端和服务器的 CPU 及内存占用。

请求优先级

把 HTTP 消息分解为很多独立的帧之后,就可以通过优化这些帧的交错和传输顺序,进一步提升性能。为了做到这一点,每个流都可以带一个 31 比特的优先值:

  • 0 表示最高优先级
  • 2^31 - 1 表示最低优先级

有了这个优先值,客户端和服务器就可以在处理不同的流时采取不同的策略,以最优的方式发送流、消息和帧。具体来讲,服务器可以根据流的优先级,控制资源分配(CPU、内存、带宽),而在响应数据准备好之后,优先将最高优先级的帧发送给客户端。

服务器推送

HTTP/2.0 新增的一个强大的新功能,就是服务器可以对一个客户端请求发送多个响应。换句话说,除了对最初请求的响应外,服务器还可以额外向客户端推送资源,而无需客户端明确的请求。

建立 HTTP/2.0 连接后,客户端与服务器交换 SETTINGS 帧,借此可以限定双向并发的流的最大数量。因此,客户端可以限定推送流的数量,或者通过把这个值设置为 0 而完全禁用服务器推送。

为什么需要这样一个机制呢?通常的 Web 应用都由几十个资源组成,客户端需要分析服务器提供的文档才能逐个找到它们。那为什么不让服务器提前就把这些资源推送给客户端,从而减少额外的时间延迟呢?服务器已经知道客户端下一步要请求什么资源了,这时候服务器推送即可派上用场。上篇文章所说的 “嵌入资源” 也可以算作服务器推送。把资源直接插入到文档中,就是把资源直接推送给客户端,而无需客户端请求。在 HTTP/2.0 中,唯一的不同就是把这个过程从应用中拿出来,放到 HTTP 协议本身来实现,而且还带来了如下好处:

  • 客户端可以缓存推送过来的资源
  • 客户端可以拒绝推送过来的资源
  • 推送资源可以由不同的页面共享
  • 服务器可以按照优先级推送资源

最后还有一点,就是推送的资源将直接进入客户端缓存,就像客户端请求了似的。不存在客户端 API 或 JavaScript 回调方法等通知机制,可以用于确定资源何时到达。整个过程对运行在浏览器中的 Web 应用来说好像根本不存在。

首部压缩

HTTP 的每一次通信都会携带一组首部,用于描述传输的资源及其属性。在 HTTP/1.x 中,这些元数据都是以纯文本形式发送的,通常会给每个请求增加 500~800 字节的负荷。如果算上 HTTP Cookie,增加的负荷通常会达到上千字节。为减少这些开销并提升性能,HTTP/2.0 会压缩首部元数据:

  • HTTP/2.0 在客户端和服务器端使用 “首部表” 来跟踪和存储之前发送的键值对,对于相同的数据,不在通过每次请求和响应发送。
  • 首部表在 HTTP/2.0 的连接存续期内始终存在,由客户端和服务器共同渐进的更新。
  • 每个新的首部键值对要么被追加到当前表的末尾,要么替换表中之前的值

于是,HTTP/2.0 连接的两端都知道已经发送了哪些首部,这些首部的值是什么,从而可以针对之前的数据只编码发送差异数据。

请求与响应首部的定义在 HTTP/2.0 中基本没有改变,只是所有首部键必须全部小写,而且请求行要独立为 :method、:scheme、:host 和 :path 等这些键值对。

在前面的例子中,第二次请求只需要发送变化了的路径首部(:path),其他首部没有变化,不用再发送了。这样就可以避免传输冗余的首部,从而显著减少每个请求的开销。

二进制分帧层简介

HTTP/2.0 的根本改进还是新增的长度前置的二进制分帧层。与 HTTP/1.x 使用换行符分隔纯文本不同,二进制分帧层更加简洁,通过代码处理起来更简单也更有效。

建立了 HTTP/2.0 连接后,客户端与服务器会通过交换帧来通信,帧是基于这个新协议通信的最小单位。所有帧都共享一个 8 字节的首部,如下图所示,其中包含帧的长度、类型、标志,还有一个保留位和一个 31 位的流标识符。

  • 16 位的长度前缀意味着一帧大约可以携带 64 KB 数据,不包括 8 字节首部
  • 8 位的类型字段决定如何解释帧其余部分的内容
  • 8 位的标志字段允许不同的帧类型定义特定于帧的消息标志
  • 1 位的保留字段始终置为 0
  • 31 位的流标识符唯一标识 HTTP/2.0 的流

知道了帧类型,解析器就知道该如何解析帧的其余内容了,HTTP/2.0 规定了如下帧类型:

  • DATA:用于传输 HTTP 消息体
  • HEADERS:用于传输关于流的额外的首部字段
  • PRIORITY:用于指定或重新指定引用资源的优先级
  • RST_STREAM:用于通知流的非正常终止
  • SETTINGS:用于通知两端通信方式的配置数据
  • PUSH_PROMISE:用于发出创建流和服务器引用资源的要约
  • PING:用于计算往返时间,执行 “活性” 检查
  • GOAWAY:用于通知对端停止在当前连接中创建流
  • WINDOW_UPDATE:用于针对个别流或个别连接实现流量控制
  • CONTINUATION:用于继续一系列首部块片段

服务器可以利用 GOAWAY 类型的帧告诉客户端要处理的最后一个流的 ID,从而消除一些请求竞争,而且浏览器也可以据此智能的重试或取消 “悬着的” 请求。这也是保证复用连接安全的一个重要和必要的功能。