HTTP/2 的首要目标是改善 Web 应用程序用户的体验。作为一个 二进制协议 ,它拥有包括轻量型、安全和快速在内的所有优势。HTTP/2 保留了原始 HTTP 协议的语义,但更改了在系统之间传输数据的方式。这些复杂细节主要由客户端和服务器管理,所以网站和应用程序无需重大更改即可享受 HTTP/2 的优势。 本文将概述 HTTP/2,包括它试图解决的问题,以及它的大量新的性能增强特性 — 包括请求/响应复用、报头压缩和服务器推送。 HTTP 的历史在深入介绍 HTTP/2 协议的细节之前,让我们回到过去并回顾一下HTTP 中的起源。 该协议于 1989 年首次曝光,以 HTTP 0.9 的形式面世。Timothy Berners-Lee 在瑞士日内瓦附近的 CERN 上首次提到它时,它仅包含 1 行代码。唯一的方法是 HTTP 0.9 不是一个正式标准,通过这种方式引用它是为了将它与随后的正式版本区分开。1996 年,推出了 HTTP 1.0 作为 IEFT 标准(依据 RFC 1945 )。1999 年,在 RFC 2616 中发布了 HTTP 1.1。第一个主要版本中的缺点促使人们在 1999 年进行了一次小幅修订,引入了大量可选特性和零碎细节 — 并消除了一些不好的方面。 几乎没有浏览器(或服务器)实现会采用该协议的每个方面,这导致不同浏览器间的用户体验不一致。显然,浏览器供应商无法实现 HTTP 1.1 中引入的 HTTP 管道的性能增强特性。 您可以亲自查看:在最近的一次 演示 中,Cloudflare 通过 HTTP 1.1 和 HTTP/2 加载了 200 个图像切片,然后比较了加载时间。 随着网络的使用变得更加普遍,性能需求呈指数级增长,而对 HTTP 的需求阻碍了性能提升。开发人员开始创建工具来克服该协议的不足。例如,HTTP 对 TCP 套接字的低效使用限制了性能,所以开发人员退而使用精心设计的服务器架构(racks of servers)来满足应用程序需求。从这个角度讲,未能让管道正常工作,促使人们开始着重反思 HTTP 的需求。 15 年后,才成立了 HTTPbis 工作组来正式识别该协议的棘手问题,并最终起草对 HTTP/2 的预期。怀着显著改善最终用户对 HTTP 1.1 延迟的认知的使命,该工作组的协议推荐包含针对 “线头阻塞” 问题、报头压缩和服务器推送的精选解决方案。 RFC 7540 (HTTP/2) 和 7541 (HPACK) 的结合,预示着 Web 应用程序性能将急剧提升。 HTTP 的现状自万维网诞生以来,网页变得愈加复杂。 第一批网页 非常简单,仅包含文本:没有图像,没有 CSS,没有 JavaScript,只有普通的 HTML。快进到今天,平均每个网页就包含 100 多个下载资源,大小约为 2,500 KB。总传输大小自 2012 年 5 月 以来增长了 250%,这种持续增长没有出现缓和迹象。 图 1. 总传输大小和总请求数 (2012-2017),来源: HTTPArchive![]()
工具和变通方案尽管互联网确实能快速提供高度复杂的内容,但出现这样的结果并不是因为 HTTP 1.1 协议( 尽管 采用了该协议)。在当前版本中,HTTP 无法满足如今的 Web 体验需求。因此,Web 开发人员针对这些性能问题提供了一系列变通方案。让我们来看一些比较流行的工具和它们修补的问题。 线头阻塞HTTP 1.0 仅允许通过一个 TCP 连接发出一个请求。这引发了所谓的 “线头阻塞” 问题,迫使浏览器等待缓慢的响应。HTTP 1.1 通过 管道 解决了这个问题,管道使浏览器能并行发出多个请求。但是,浏览器供应商很难实现管道,而且大多数浏览器(包括 Firefox)在发布时都会默认禁用该特性。Chrome 甚至完全删除了它。 多个 TCP 连接打开 TCP 连接需要很高的成本,而且我们对客户端应如何使用它们知之甚少。唯一的协议规定是,每个主机最多可以打开 2 个连接。由于只有 2 个 TCP 连接,开发人员为了能够展示一个现代页面需要竞争这两个名额 — 所以他们找到了一种方法来绕过这一限制。 通过使用一种称为 域分片(domain sharding) 的流行技术,开发人员能创建多个主机,每个主机提供一个网站所需资源的一部分。切分已变得非常普遍,网页加载期间打开的平均 TCP 连接数量也因此达到约 35 个(来源: HTTPArchive )。 浏览器供应商不甘示弱,他们也违反了该协议,任意增加浏览器实现中允许的开放连接数量。这有助于并行化各个浏览器中的资源加载,但没有充分利用 TCP 套接字。下表显示了每个主机名允许打开的端口的最高数量,以及最流行的 3 个浏览器在这方面的不同。 表 1. 并行打开的 TCP 连接的最大数量(来源: browserscope.org )
浏览器实现中的不一致意味着,用户冲浪体验的质量取决于他们选择的浏览器,而不是网站的设计和构思有多精巧。 资源内联和级联为了追求更高性能,Web 应用程序开发人员采用的聪明技巧并不只有域切分。
这些技术都不可取,尤其是从设计角度讲。在这两种情况下,页面的结构都与样式组合在一起,图像解码也会消耗很多时间。缓存也无法轻松实现。 但是,如果目标只是减少请求的文件数量,那么这些变通方案是成功的。随着文件请求减少,需要打开的 TCP 套接字也会减少。 最吸引人的特性HTTP/2 的大多数实用特性归功于 Google 在 SPDY 协议上开展的工作。在 HTTPbis 工作组开始起草 HTTP/2 RFC 的第一个版本时,SPDY 已证明一个主要 HTTP 版本更新切实可行。因为已经部署并开始采用 SPDY,所以有证据表明更新的协议在自然环境下具有更高的性能。 HTTP/2 成功的关键在于,它实现了显著的性能改善,同时保持了 HTTP 范例,以及 HTTP 和 HTTPS 模式。该工作组规定,向 HTTP/2 的迁移必须透明,而且使用者不会受到任何影响。 该协议最吸引人的特性包括:
让我们来查看每个特性。 新升级路径HTTP/2 升级路径与标准路径稍有不同,省去了一些协商。对于基于 HTTP/2 的安全连接,无法通过升级标头请求切换协议,并收到一条让人安心的“101 switching”HTTP 状态。相反,通过使用一个名为应用层协议协商 (ALPN) 的新扩展,客户端向服务器告知它能理解的通信协议(按偏好排序)。服务器然后使用该列表中它也理解的第一个协议作为响应。 SPDY 需要一个安全连接,虽然社区迫于压力会建立这样的连接,但 HTTP/2 规范没有强制要求这么做。但是,所有主要浏览器供应商都仅在 TLS 上实现 HTTP/2,而且不支持不安全的连接。这实际上会迫使 Web 应用程序实现者对所有 HTTP/2 流量使用 TLS(来源: caniuse.com)。curl 用户仍可采用通过 HTTP 升级标头的升级路径,因为它将实现既明确又安全的连接。 二进制协议或许 HTTP/2 的最重要改变是转换为二进制协议。对于开发人员,这可以说是性能增强的焦点。新协议称为 二进制分帧层(binary framing layer) ,它重新设计了编码机制,而没有修改方法、动词和标头的熟悉语义。 最重要的是,所有通信都在单个 TCP 连接上执行,而且该连接在整个对话期间一直处于打开状态。这可能得益于二进制协议将通信分解为帧的方式:这些帧交织在客户端与服务器之间的双向逻辑流中。 连接的拓扑结构正如我提到的,在 HTTP/2 的新范例中,仅在客户端与服务器之间建立了一个 TCP 连接,而且该连接在交互持续期间一直处于打开状态。在此连接上,消息是通过逻辑流进行传递的。一条 消息 包含一个完整的帧序列。在经过整理后,这些帧表示一个响应或请求。 图 2 演示了连接组件之间的关系,展示了一个用于建立多个流的连接。在流 1 中,发送了一条请求消息,并返回了相应的响应消息。 图 2. HTTP/2 连接的拓扑结构![]()
我们将分别查看每个概念。 连接和流仅与一个对等节点建立一个连接,并在该连接上传输多个流。因为流可以交织,所以可以同时快速的传输多个流。 消息消息 是一组帧。在对等节点上重建这些帧时,它们形成一个完整的请求或响应。特定消息的帧在同一个流上发送,这意味着一个请求或响应只能映射到一个可识别的流。 帧帧 是通信的基本单位。每个帧有一个标头,其中包含帧的长度和类型、一些布尔标志、一个保留位和一个流标识符,如图 3 所示。 图 3. 帧分解![]()
长度length 字段 记录帧的大小,它最多可在一个 类型type 字段 标识帧的用途,可以是以下 10 种类型之一:
参见规范的 11.2 节 了解每种帧类型的功能的更多细节。 标志flag 字段 是一个布尔值,指定帧的状态信息:
所有其他帧类型都无法设置标志。 流标识符流标识符 用于跟踪逻辑流的帧成员关系。成员每次仅属于一条消息和流。流可以提供优先级建议,这有助于确定分配给它的网络资源。我稍后会更详细地解释流优先化。 请求/响应复用单一 TCP 连接的问题在于,一次只能发出一个请求,所以客户端必须等到收到响应后才能发出另一个请求。这就是 “线头阻塞” 问题。正如之前讨论的,典型的变通方案是打开多个连接;每个请求一个连接。但是,如果可以将消息分解为更小的独立部分并通过连接发送,此问题就会迎刃而解。 这正是 HTTP/2 希望达到的目标。将消息分解为帧,为每帧分配一个流标识符,然后在一个 TCP 连接上独立发送它们。此技术实现了完全双向的请求和响应消息复用,如下图所示。 图 4. 在 TCP 连接上交织的帧![]()
图 4 中的图解显示在一个连接上快速传输了 3 个流。服务器发送两个响应,客户端发送一个请求。 在流 1 中,服务器为一个响应发送 图 5. HTTP/2 将请求/响应帧交织在一起![]()
所有帧在另一端重新组装,以形成完整的请求或响应消息。 帧交织有许多好处:
图 6. 将 HTTP 请求映射到 HTTP/2 帧![]()
我们将左侧的一个 HTTP 请求映射到右侧的一个 在
接下来,让我们来看看该请求的响应。 将 HTTP 请求映射到帧图 7 的左侧是一个 HTTP 1.1 标头响应。右侧是使用两个 HTTP/2 帧表示的同一个响应: 图 7. 将 HTTP 响应映射到 HTTP/2 帧![]()
在 报头压缩HTTP/2 协议拥有配套的 HPACK。HPACK 的目的是减少客户端请求与服务器响应之间的标头信息重复所导致的开销。 报头压缩 的实现方式是,要求客户端和服务器都维护之前看见的标头字段的列表。未来在构建引用了已看见标头列表的消息时可以使用此列表。 图 8. 压缩同一个连接上的两个请求的标头![]()
在图 8 中的两个请求中,标头信息是重复的。唯一的不同在请求的资源上(已采用黄色突出显示)。HPACK 报头压缩可以在这里派上用场。在第一个请求后,它仅需发送与前一个标头的不同之处,因为服务器保留着以前看见的标头的列表。除非设置了标头值,否则会假设后续请求拥有与之前的请求相同的标头值。 流优先化消息帧通过流进行发送。每个流都分配了一个优先级,用于确定它的处理顺序,以及它将收到的资源量。 将该优先级输入到给定流的标头帧或优先级帧中,优先级可以是 0 到 256 之间的任何数字。 可以定义依赖关系,允许在一个资源之前加载另一个资源。也可以将优先级组合到一个 依赖树 中,让开发人员对分配给每个流的重要性有更多控制权。 图 9. 用于流优先化的依赖树![]()
在图 9 中,字母表示流标识符,数字表示分配给每个流的权重。树的根是流 A,首先会向它分配资源,然后才向依赖它的流 B 和 C 分配资源。为流 B 分配了 40% 的可用资源,流 C 收到了 60% 的可用资源。流 C 是流 D 和 E 的父流,二者分别从其父流收到相同的资源配额。 流优先级仅是对服务器的建议,可以动态更改或完全忽略。在起草 HTTP/2 协议 | ||||||||
|
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系
[邮箱地址] 删除
|