转载自 | 小米运维(公众号 ID:MI-SRE)
HTTP/2 协议简介
HTTP/2 是现行 HTTP 协议(HTTP/1.x)的替代,但它不是重写,HTTP 方法 / 状态码 / 语义都与 HTTP/1.x 一样。不过,HTTP/2 修改了数据格式化(分帧)以及在客户端与服务器间传输的方式。HTTP/2 基于 SPDY3,专注于性能,最大的一个目标是在用户和网站间只用一个连接。
HTTP/2 通过支持完整的请求与响应复用来减少延迟,通过有效压缩 HTTP 报头字段将协议开销降至最低,同时增加对请求优先级和服务器推送的支持。
HTTP/2 协议由以下两个 RFC 组成:
- RFC 7540 - Hypertext Transfer Protocol Version 2 (HTTP/2); RFC 7541 -
- HPACK: Header Compression for HTTP/2;
HTTP/2 解决的问题
影响一个 HTTP 网络请求的因素主要有两个:带宽和延迟。在当今的网络情况下,带宽一般不再是瓶颈,所以我们主要讨论下延迟。延迟一般由下面几个因素所造成:
- 浏览器线头阻塞(Head-Of-Line Blocking):浏览器会因为一些原因阻塞请求。
- DNS 查询。
- 建立连接(Initial connection):HTTP 基于 TCP 协议,TCP 的 3 次握手和慢启动极大增加延迟。
HTTP/1.x 中存在的问题
连接无法复用:
连接无法复用会导致每次请求都经历三次握手和慢启动。三次握手在高延迟的场景下影响较明显,慢启动则对文件类大请求影响较大。
- HTTP/1.0 传输数据时,每次都需要重新建立连接,增加延迟。
- HTTP/1.1 虽然加入 keep-alive,可以复用一部分连接,但域名分片等情况下仍然需要建立多个 connection,耗费资源,给服务器带来性能压力。
线头阻塞(Head-Of-Line Blocking):
导致带宽无法被充分利用,以及后续健康请求被阻塞。HOLB 是指在 HTTP/1.x 中,由于服务器必须按接受请求的顺序发送响应的规则限制,那么假设浏览器在一个(tcp)连接上发送了两个请求,那么服务器必须等第一个请求响应完毕才能发送第二个响应——HOLB。虽然现在浏览器允许每个 origin 建立 6 个 connection,但大量网页动辄几十个或上百个资源,HOLB 依然是主要问题。
协议开销大:
HTTP/1.x 中 header 内容过大(每次请求 header 基本不怎么变化),增加了传输的成本。
安全因素:
在 HTTP 中传输的内容都是明文,客户端和服务端双方无法验证身份。
HTTP/2 解决的问题
连接复用:
在用户和网站之间只用一个连接,避免后续建立连接过程中的几个往返和慢启动,同时减少了服务器的资源消耗。
没有线头阻塞:
采用新的二进制分帧层的机制,组成消息的帧可以乱序发送,帧到达对端重新组装,不需要等待前面的帧到达后再发送。
报头压缩:
HTTP/2 协议中采用 HPACK 来压缩请求头和响应头,降低协议开销。
更加安全:
当前主流浏览器,都只支持基于 HTTPS 部署的 HTTP/2。
HTTP/2 协议中的新特性
二进制分帧层
HTTP 2.0 性能增强的核心,全在于新增的二进制分帧层,它定义了如何封装 HTTP 消息并在客户端和服务器之间传输,和 HTTP/1.x 对比如下图:
这里所谓的 “层”,指的是位于套接字接口与应用可见的高级 HTTP API 之间一个经过优化的新编码机制:HTTP 的语义(包括各种动词、方法、标头)都不受影响,不同的是传输期间对它们的编码方式变了。HTTP/1.x 协议以换行符作为纯文本的分隔符,而 HTTP/2 将所有传输的信息分割为更小的消息和帧,并采用二进制格式对它们编码。
这样一来,客户端和服务器为了相互理解,都必须使用新的二进制编码机制:HTTP/1.x 客户端无法理解只支持 HTTP/2 的服务器,反之亦然。不过不要紧,现有的应用不必担心这些变化,因为客户端和服务器会替我们完成必要的分帧工作。
数据流、消息和帧
新的二进制分帧机制改变了客户端与服务器之间交换数据的方式。 为了说明这个过程,我们需要了解 HTTP/2 的三个概念:
- 数据流:已建立的连接内的双向字节流,可以承载一条或多条消息。
- 消息:与逻辑请求或响应消息对应的完整的一系列帧。
- 帧:HTTP/2 通信的最小单位,每个帧都包含帧头,至少也会标识出当前帧所属的数据流。
这些概念的关系总结如下:
- 所有通信都在一个 TCP 连接上完成,此连接可以承载任意数量的双向数据流。
- 每个数据流都有一个唯一的标识符和可选的优先级信息,用于承载双向消息。
- 每条消息都是一条逻辑 HTTP 消息(例如请求或响应),包含一个或多个帧。
帧是最小的通信单位,承载着特定类型的数据,例如 HTTP 标头、消息负载等等。 来自不同 数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装,如下图:
简言之,HTTP/2 将 HTTP 协议通信分解为二进制编码帧的交换,这些帧对应着特定数据流中的消息。所有这些都在一个 TCP 连接内复用。这是 HTTP/2 协议所有其他功能和性能优化的基础。
多路复用
在 HTTP/1.x 中,如果客户端要想发起多个并行请求以提升性能,则必须使用多个 TCP 连接。这是 HTTP/1.x 交付模型的直接结果,该模型可以保证每个连接每次只交付一个响应(响应排队)。更糟糕的是,这种模型也会导致队首阻塞,从而造成底层 TCP 连接的效率低下。
HTTP/2 中新的二进制分帧层突破了这些限制,实现了完整的请求和响应复用:客户端和服务器可以将 HTTP 消息分解为互不依赖的帧,然后交错发送,最后再在另一端把它们重新组装起来。
上图捕捉了同一个连接内并行的多个数据流。客户端正在向服务器传输一个 DATA 帧(数据流 5),与此同时,服务器正向客户端交错发送数据流 1 和数据流 3 的一系列帧。因此,一个连接上同时有三个并行数据流。
将 HTTP 消息分解为独立的帧,交错发送,然后在另一端重新组装是 HTTP 2 最重要的一项增强。事实上,这个机制会在整个网络技术栈中引发一系列连锁反应,从而带来巨大的性能提升,让我们可以:
- 并行交错地发送多个请求,请求之间互不影响。
- 并行交错地发送多个响应,响应之间互不干扰。
- 使用一个连接并行发送多个请求和响应。
- 不必再为绕过 HTTP/1.x 限制而做很多工作(对 HTTP/1.x 进行优化,例如级联文件、image sprites 和域名分片。
- 消除不必要的延迟和提高现有网络容量的利用率,从而减少页面加载时间。
- 等等…
HTTP/2 中的新二进制分帧层解决了 HTTP/1.x 中存在的队首阻塞问题,也消除了并行处理和发送请求及响应时对多个连接的依赖。结果,应用速度更快、开发更简单、部署成本更低。
请求重置
HTTP 1.1 的有一个缺点是:当一个含有确切值的 Content-Length 的 HTTP 消息被送出之后,你就很难中断它了。当然,通常你可以断开整个 TCP 连接(但也不总是可以这样),但这样导致的代价就是需要通过三次握手来重新建立一个新的 TCP 连接。
一个更好的方案是只终止当前传输的消息并重新发送一个新的。在 HTTP/2 里面,我们可以通过发送 RST_STREAM 帧来实现这种需求,从而避免浪费带宽和中断已有的连接。
请求优先级
将 HTTP 消息分解为很多独立的帧之后,我们就可以复用多个数据流中的帧,客户端和服务器交错发送和传输这些帧的顺序就成为关键的性能决定因素。为了做到这一点,HTTP/2 标准允许每个数据流都有一个关联的权重和依赖关系:
- 可以向每个数据流分配一个介于 1 至 256 之间的整数。
- 每个数据流与其他数据流之间可以存在显式依赖关系。
数据流依赖关系和权重的组合让客户端可以构建和传递 “优先级树”,表明它倾向于如何接收响应。反过来,服务器可以使用此信息通过控制 CPU、内存和其他资源的分配设定数据流处理的优先级,在资源数据可用之后,带宽分配可以确保将高优先级响应以最优方式传输至客户端。
如上图,HTTP/2 内的数据流依赖关系通过将另一个数据流的唯一标识符作为父项引用进行声明;如果忽略标识符,相应数据流将依赖于 “根数据流”。声明数据流依赖关系指出,应尽可能先向父数据流分配资源,然后再向其依赖项分配资源。换句话说,“请先处理和传输响应 D,然后再处理和传输响应 C”。
共享相同父项的数据流(即,同级数据流)应按其权重比例分配资源。 例如,如果数据流 A 的权重为 12,其同级数据流 B 的权重为 4,那么要确定每个数据流应接收的资源比例,请执行以下操作:
将所有权重求和:4 + 12 = 16
将每个数据流权重除以总权重:A = 12/16, B = 4/16 因此,数据流 A 应获得四分之三的可用资源,数据流 B 应获得四分之一的可用资源;数据流 B 获得的资源是数据流 A 所获资源的三分之一。 我们来看一下上图中的其他几个动手示例:顺序为从左到右:
数据流 A 和数据流 B 都没有指定父依赖项,依赖于显式 “根数据流”;A 的权重为 12,B 的权重为 4。因此,根据比例权重:数据流 B 获得的资源是 A 所获资源的三分之一。
数据流 D 依赖于根数据流;C 依赖于 D。因此,D 应先于 C 获得完整资源分配。权重不重要,因为 C 的依赖关系拥有更高的优先级。
数据流 D 应先于 C 获得完整资源分配;C 应先于 A 和 B 获得完整资源分配;数据流 B 获得的资源是 A 所获资源的三分之一。
数据流 D 应先于 E 和 C 获得完整资源分配;E 和 C 应先于 A 和 B 获得相同的资源分配;A 和 B 应基于其权重获得比例分配。
如上面的示例所示,数据流依赖关系和权重的组合明确表达了资源优先级,这是一种用于提升浏览性能的关键功能,网络中拥有多种资源类型,它们的依赖关系和权重各不相同。不仅如此,HTTP/2 协议还允许客户端随时更新这些优先级,进一步优化了浏览器性能。换句话说,我们可以根据用户互动和其他信号更改依赖关系和重新分配权重。
注:数据流依赖关系和权重表示传输优先级,而不是要求,因此不能保证特定的处理或传输顺序。即,客户端无法强制服务器通过数据流优先级以特定顺序处理数据流。 尽管这看起来违反直觉,但却是一种必要行为。 我们不希望在优先级较高的资源受到阻止时,还阻止服务器处理优先级较低的资源。
每个来源一个连接
有了新的分帧机制后,HTTP/2 不再依赖多个 TCP 连接去并行复用数据流;每个数据流都拆分成很多帧,而这些帧可以交错,还可以分别设定优先级。因此,所有 HTTP/2 连接都是永久的,而且仅需要每个来源一个连接,随之带来诸多性能优势。
大多数 HTTP 传输都是短暂且急促的,而 TCP 则针对长时间的批量数据传输进行了优化。 通过重用相同的连接,HTTP/2 既可以更有效地利用每个 TCP 连接,也可以显著降低整体协议开销。不仅如此,使用更少的连接还可以减少占用的内存和处理空间,也可以缩短完整连接路径(即,客户端、可信中介和源服务器之间的路径)这降低了整体运行成本并提高了网络利用率和容量。 因此,迁移到 HTTP/2 不仅可以减少网络延迟,还有助于提高通量和降低运行成本。
在 HTTP/2 RFC 文档中建议实现时客户端不应该在给定的目的地上打开多个 HTTP/2 连接,目的地是由给定的 URI 确定的 IP 地址及 TCP 端口,或者配置的代理 IP 和端口。当然客户端可以使用不相同的服务端名称标识值或者提供不一样的 ssl 证书对相同的 IP 地址及 TCP 端口打开多个连接,但应该避免对相同的配置上创建多个连接。
注:连接数量减少对提升 HTTPS 部署的性能来说是一项特别重要的功能:可以减少开销较大的 TLS 连接数、提升会话重用率,以及从整体上减少所需的客户端和服务器资源。
流量控制
流量控制是一种阻止发送方向接收方发送大量数据的机制,以免超出后者的需求或处理能力:发送方可能非常繁忙、处于较高的负载之下,也可能仅仅希望为特定数据流分配固定量的资源。例如,客户端可能请求了一个具有较高优先级的大型视频流,但是用户已经暂停视频,客户端现在希望暂停或限制从服务器的传输,以免提取和缓冲不必要的数据。再比如,一个代理服务器可能具有较快的下游连接和较慢的上游连接,并且也希望调节下游连接传输数据的速度以匹配上游连接的速度来控制其资源利用率;等等。
不过,由于 HTTP/2 数据流在一个 TCP 连接内复用,TCP 流控制既不够精细,也无法提供必要的应用级 API 来调节各个数据流的传输。为了解决这一问题,HTTP/2 提供了一组简单的构建块,这些构建块允许客户端和服务器实现其自己的数据流和连接级流量控制:
- 流量控制具有方向性,每个接收方都可以根据自身需要选择为每个数据流和整个连接设置任意的窗口大小。
- 流量控制基于窗口更新帧进行,即接收方广播自己准备接收某个数据流的多少字节,以及对整个连接要接收多少字节。
- 流量控制窗口大小通过 WINDOW_UPDATE 帧更新,这个字段制定了流 ID 和窗口递增值。
- 流量控制可以由接收方禁用,包括针对个别的流和针对整个连接。
- 流量控制基于每一跳进行,而非端到端控制。即,可信中介可以使用它来控制资源使用,以及基于自身条件和启发式算法实现资源分配机制。
HTTP/2 未指定任何特定算法来实现流量控制。不过,它提供了简单的构建块并推迟了客户端和服务器实现,可以实现自定义策略来调节资源使用和分配,以及实现新传输能力,同时提升网络应用的实际性能和感知性能。
例如,应用层流量控制允许浏览器仅提取一部分特定资源,通过将数据流流控制窗口减小为零来暂停提取,稍后再行恢复。换句话说,它允许浏览器提取图像预览或首次扫描结果,进行显示并允许其他高优先级提取继续,然后在更关键的资源完成加载后恢复提取。
服务器推送
HTTP/2 新增的另一个强大的新功能是,服务器可以对一个客户端请求发送多个响应。 换句话说,除了对最初请求的响应外,服务器还可以向客户端推送额外资源(如下图),而无需客户端明确地请求。
注:HTTP/2 打破了严格的请求 - 响应语义,支持一对多和服务器发起的推送工作流,在浏览器内外开启了全新的互动可能性。这是一项使能功能,对我们思考协议、协议用途和使用方式具有重要的长期影响。
为什么在浏览器中需要一种此类机制呢?一个典型的网络应用包含多种资源,客户端需要检查服务器提供的文档才能逐个找到它们。那为什么不让服务器提前推送这些资源,从而减少额外的延迟时间呢?服务器已经知道客户端下一步要请求什么资源,这时候服务器推送即可派上用场。
事实上,如果在网页中内联过 CSS、JavaScript,或者通过数据 URI 内联过其他资产,那么就已经亲身体验过服务器推送了。对于将资源手动内联到文档中的过程,我们实际上是在将资源推送给客户端,而不是等待客户端请求。使用 HTTP/2,我们不仅可以实现相同结果,还会获得其他性能优势。 推送资源可以进行以下处理:
- 由客户端缓存
- 在不同页面之间重用
- 与其他资源一起复用
- 由服务器设定优先级
- 被客户端拒绝
PUSH_PROMISE 101
所有服务器推送数据流都由 PUSH_PROMISE 帧发起,表明了服务器向客户端推送所述资源的意图,并且需要先于请求推送资源的响应数据传输。这种传输顺序非常重要:客户端需要了解服务器打算推送哪些资源,以免为这些资源创建重复请求。满足此要求的最简单策略是先于父响应(即,DATA 帧)发送所有 PUSH_PROMISE 帧,其中包含所承诺资源的 HTTP 标头。
在客户端接收到 PUSH_PROMISE 帧后,它可以根据自身情况选择拒绝数据流(通过 RST_STREAM 帧)。 (如果资源已经位于缓存中,可能会发生这种情况。) 这是一个相对于 HTTP/1.x 的重要提升。 相比之下,使用资源内联(一种受欢迎的 HTTP/1.x“优化”)等同于 “强制推送”:客户端无法选择拒绝、取消或单独处理内联的资源。
使用 HTTP/2,客户端仍然完全掌控服务器推送的使用方式。客户端可以限制并行推送的数据流数量;调整初始的流控制窗口以控制在数据流首次打开时推送的数据量;或完全停用服务器推送。这些优先级在 HTTP/2 连接开始时通过 SETTINGS 帧传输,可能随时更新。
推送的每个资源都是一个数据流,与内嵌资源不同,客户端可以对推送的资源逐一复用、设定优先级和处理。 浏览器强制执行的唯一安全限制是,推送的资源必须符合原点相同这一政策:服务器对所提供内容必须具有权威性。
报头压缩
每个 HTTP 传输都承载一组报头,这些报头说明了传输的资源及其属性。 在 HTTP/1.x 中,此元数据始终以纯文本形式,通常会给每个传输增加 500–800 字节的开销。如果使用 HTTP Cookie,增加的开销有时会达到上千字节。为了减少此开销和提升性能, HTTP/2 使用 HPACK 压缩格式压缩请求和响应标头元数据,这种格式采用两种简单但是强大的技术:
- 这种格式支持通过静态 Huffman 编码对传输的标头字段进行编码,从而减小了各个传输的大小。
- 这种格式要求客户端和服务器同时维护和更新一个包含之前见过的标头字段的索引列表(换句话说,它可以建立一个共享的压缩上下文),此列表随后会用作参考,对之前传输的值进行有效编码。
利用 Huffman 编码,可以在传输时对各个值进行压缩,而利用之前传输值的索引列表,我们可以通过传输索引值的方式对重复值进行编码,索引值可用于有效查询和重构完整的标头键值对。
作为一种进一步优化方式,HPACK 压缩上下文包含一个静态表和一个动态表:静态表在规范中定义,并提供了一个包含所有连接都可能使用的常用 HTTP 标头字段(例如,有效标头名称)的列表;动态表最初为空,将根据在特定连接内交换的值进行更新。因此,为之前未见过的值采用静态 Huffman 编码,并替换每一侧静态表或动态表中已存在值的索引,可以减小每个请求的大小。
注:在 HTTP/2 中,请求和响应标头字段的定义保持不变,仅有一些微小的差异:所有标头字段名称均为小写,请求行现在拆分成各个 :method、:scheme、:authority 和 :path 伪标头字段。
至于 HPACK 压缩的详细介绍,请点击这里:HTTP/2 头部压缩技术介绍或者官方 RFC。
HTTP/2 协议实际性能测试与分析
为了测试 HTTP/2 对 web 访问的性能提升,本人借助 bbs 产品线的 miui 官方网站,开启了 tengine 的 HTTP/2 的支持,取一周的访问数据与 HTTPS、HTTP 访问数据进行对比分析,详细结果如下:
采集数据量
响应类型分布
请求类别 | http | https | http2 |
---|---|---|---|
2xx | 945144 | 927482 | 505702 |
3xx | 243075 | 258331 | 681997 |
4xx | 2372 | 4750 | 2813 |
5xx | 9 | 37 | 88 |
sum | 1190600 | 1190600 | 1190600 |
延迟数据分析
2xx 请求各个响应时间段占比(基于 nginx log 的 request_time 数据)
时间 | http | https | http2 |
---|---|---|---|
<50ms | 79.75% | 78.89% | 82.60% |
<100ms | 87.24% | 87.47% | 89.86% |
<150ms | 91.12% | 91.90% | 93.19% |
<200ms | 92.72% | 93.82% | 94.54% |
<2s | 98.99% | 99.71% | 99.48% |
2xx 请求响应时间大于 7 秒的数量 (基于 nginx log 的 request_time 数据)
时间 | http | https | http2 |
---|---|---|---|
>7s | 1960(0.207%) | 603(0.065%) | 950(0.187%) |
>10s | 1519(0.160%) | 420(0.045%) | 638(0.126%) |
>30s | 594(0.062%) | 165(0.017%) | 190(0.037%) |
>60s | 259(0.027%) | 104(0.011%) | 97(0.019%) |
2xx 请求后端响应时间超过 7 秒的数量(基于 nginx log 的 upstream_response_time)
时间 | http | https | http2 |
---|---|---|---|
>7s | 68(0.007%) | 88(0.017%) | 58(0.006%) |
带宽数据分析
301 请求响应大小(基于 nginx 的 request_length 和 bytes_sent 数据)
/static/image/common/miui9.jpg | 请求数 | 总大小(byte) | avg(byte) | 请求总大小(byte) | 请求avg(byte) |
---|---|---|---|---|---|
http | 2722 | 1460146 | 536 | 3194589 | 1173 |
https | 4695 | 2618278 | 415 | 8019924 | 1708 |
http2 | 16239 | 6751609 | 557 | 1209570 | 74 |
200 请求响应大小(基于 nginx log 的 request_length 和 bytes_sent 数据)
/favicon.ico | 请求数 | 响应总大小(byte) | 响应avg(byte) | 请求总大小(byte) | 请求avg(byte) |
---|---|---|---|---|---|
http | 17658 | 23552229 | 1413 | 15300656 | 866 |
https | 117178 | 165623779 | 1413 | 122356406 | 1044 |
http2 | 80856 | 105496656 | 1304 | 10015908 | 123 |
来源分析
客户端分析(基于 nginx log 的 user_agent 数据)
协议 | chrome | MSIE | safari | FireFox | Crawlers | others | unknown | |
---|---|---|---|---|---|---|---|---|
http | 23.53% | 4.26% | 3.56% | 0.67% | 4.01% | 15.07% | 48.22% | |
https | 79.24% | 5.93% | 3.67% | 1.54% | 7.87% | 1.42% | 0.1% | |
http2 | 88.57% | 4.93% | 1.91% | 1.57% | 0% | 2.9% | 0.03% |
平台分析(基于 nginx log 的 user_agent 数据)
协议 | Android | Windows | Linux | IOS | Darwin | unknown | |
---|---|---|---|---|---|---|---|
http | 28.4% | 16.07% | 0.29% | 2.46% | 0.12% | 51.86% | |
https | 64.85% | 24.65% | 0.62% | 0.54% | 0.29% | 9.05% | |
http2 | 40.06% | 57.62% | 1.49% | 0.44% | 0% | 0.02% |
结论
- 典型网络 RTT 下(50~200ms),HTTP/2 对网络延迟性能有一定程度的提升,优于 HTTP/1.x 和 HTTPS。
- 在 2xx 和 3xx 请求下 HTTP/2 利用 HPACK 机制压缩响应头和请求头,有效降低了请求和响应的大小,节省了流量,降低了协议消耗。
- 目前 HTTP/2 大部分流量来自于 chrome,其对 HTTP/2 的支持性也是最好的。
HTTP/2 协议部署建议
建议一:现在是否需要迁移到 HTTP/2
实现 HTTP/2 很简单,不过,HTTP/2 并不是万能的银弹,它只对某些 Web 应用有用,对另外一些则没那么有用。
如果你使用 SSL/TLS(以后简称 TLS),那么 HTTP/2 可以提升网站性能。如果你没有,那在使用 HTTP/2 之前要先支持 TLS。这时候,使用 TLS 的性能损耗大致可以被使用 HTTP/2 的性能提升抵销。不过还是建议你在实际应用之前先测试一下。
HTTP/2 有五大优势:
- 每个服务器只用一个连接。HTTP/2 对每个服务器只使用一个连接,而不是每个文件一个连接。这样,就省掉了多次建立连接的时间,这个时间对 TLS 尤其明显,因为 TLS 连接费时间。
- 加速 TLS 交付。HTTP/2 只需一次耗时的 TLS 握手,并且通过一个连接上的多路利用实现最佳性能。HTTP/2 还会压缩首部数据,省掉 HTTP/1.x 时代所需的一些优化工作,比如拼接文件,从而提高缓存利用率。
- 简化 Web 应用。使用 HTTP/2 可以让 Web 开发者省很多事,因为不用再做那些针对 HTTP/1.x 的优化工作了。
- 适合内容混杂的页面。HTTP/2 特别适合混合了 HTML、CSS、JavaScript、图片和有限多媒体的传统页面。浏览器可以优先安排那些重要的文件请求,让页面的关键部分先出现,快出现。
- 更安全。通过减少 TLS 的性能损失,可以让更多应用使用 TLS,从而让用户信息更安全。
相应地,HTTP/2 也有五个不足之处。
- 单连接开销比较大。HPACK 数据压缩算法会更新两端的查找表。这样可以让连接有状态,而破坏状态就意味着要重建查找表,另外单连接占用内存较多。
- 你可能不需要 SSL。如果你的数据不需要保护,或者已经使用 DRM 或其他编码进行保护了,那么 TLS 的安全性对你可能无所谓。
- 需要抛弃针对 HTTP/1.x 的优化。HTTP/1.x 优化在支持 HTTP/2 的浏览器中会影响性能,因此可能需要花时间把它们推倒重来。
- 对下载大文件不利。如果你的应用主要提供大文件下载或者流媒体播放,那可能不想用 TLS,而且在只有一个流的情况下,多路复用也体现不出什么优势。
- 你的客户也许不在乎。你的客户很可能不在乎他分享的自家猫咪的视频是否受到 TLS 和 HTTP/2 的保护。
总之,一切要看性能。这方面,有好消息也有坏消息。
好消息是 nginx 官方团队在内部对 NGINX 做过测试,结果从理论上能够得到印证:对于要通过典型网络延迟请求的混合内容网页,HTTP/2 的性能好于 HTTP/1.x 和 HTTPS。基于连接的 RTT,结果可以分三种情况。
- 很低的 RTT(0-20ms):HTTP/1.x、HTTP/2 和 HTTPS 基本无差别。
- 典型网络 RTT(30-250ms):HTTP/2 比 HTTP/1.x 快,而且它们都比 HTTPS 快。美国两个相邻城市间的 RTT 约为 30 ms,而东西海岸间(约 3000 英里)则约为 70 ms。东京到伦敦间最短路径的 RTT 大约 240 ms。
- 高 RTT(300ms 及以上):HTTP/1.x 比 HTTP/2 快,后者又比 HTTPS 快。
这张图显示了首次渲染的时间,也就是用户第一次在自己屏幕上看到网页内容的时间。这个时间一般认为关系到用户对网站响应速度的感知。
然而,每个网页都不相同,实际上每个用户的会话也不一样。如果你托管流媒体或提供大文件下载,那你的决定可能不一样,甚至相反。
建议二:终止 HTTP/2 和 TLS
终止协议意味着客户端使用期望的协议连接代理服务器,比如 TLS 或 HTTP/2,然后代理服务器再去连接应用服务器、数据库服务器等,但不需要使用相同的协议,如下图所示。
使用独立的服务器终止协议意味着使用多服务器架构。多服务器可能是多个物理服务器、多个虚拟服务器,或者 AWS 这样的云环境中的多个虚拟服务器实例。多服务器就比单服务器复杂,或者比应用服务器 / 数据库服务器的组合复杂。不过,多服务器架构有很多好处,而且很多流量大的网站也必须用这种架构。
配置了服务器或者虚拟服务器之后,很多事情都成为可能。新服务器可以分担其他服务器的负载,可用于负载平衡、静态文件缓存和其他用途。另外,也可以让添加和替换应用服务器或其他服务器更容易。
NGINX 和 NGINX Plus 经常被用来终止 TLS 和 HTTP/2 协议、负载平衡。已有环境不必改动,除非要把 NGINX 服务器挪到前端。
建议三:找出为 HTTP/1.x 优化的代码
在决定采用 HTTP/2 之前,首先得知道你的代码有哪些是针对 HTTP/1.x 优化过的。大概有四方面的优化。
- 分域存储。为了实现并行请求文件,你可能把文件分散到了不同的域里,CDN 会自动这么做。但分域存储会影响 HTTP/2 的性能,建议使用 HTTP/2 友好的分域存储(建议六),只针对 HTTP/1.x 用户分域。
- 雪碧图。雪碧图把很多图片拼成一个文件,然后通过代码按需取得每个图片。雪碧图在 HTTP/2 的环境下没太大用处,但还是有点用的。
- 拼接的代码文件。与使用雪碧图的原因类似,很多独立的文件也会被弄成一个,然后浏览器再从其中找到并运行需要的文件。
- 插入行内的文件。CSS 代码、JavaScript 代码,甚至图片等被直接插到 HTML 文件中的内容。这样可以减少文件传输,代价是初始 HTML 文件较大。
后面三种优化都涉及把小文件塞进一个较大的文件里,目的是减少新建连接的初始化和握手,这些操作对 TLS 而言非常费时间。
第一种优化即分域存储恰恰相反,强制打开多个连接,目的是并行地从不同的域获取文件。这两种看似矛盾的技术对于 HTTP/1.x 下的站点却十分有效。然而,要用好这两种技术,必须投入大量时间、精力和资源,用于实现、管理和运维。
在采用 HTTP/2 之前,需要找出应用了这些优化的代码,分析一下它们会不会影响你的应用设计和工作流程。这样在迁移到 HTTP/2 之后,就可以着手改造它们,甚至撤销某些优化。
建议四:部署 HTTP/2
事实上,部署 HTTP/2 并不难。如果使用 NGINX,只要在配置文件中启动相应的协议就可以了。浏览器和服务器会协商采用什么协议,如果浏览器支持 HTTP/2(而且也在使用 TLS),就会使用 HTTP/2。
配置完服务器后,使用支持 HTTP/2 浏览器的用户就会基于 HTTP/2 运行你的应用,而使用旧版本浏览器的用户则会继续使用 HTTP/1.x 运行你的应用,如下图所示。如果你的网站流量非常大,那么应该监测改变前后的性能,对于性能降低的情况,可能就得撤销更改。
注意:使用 HTTP/2 及其单连接之后,NGINX 某些配置的重要性会很明显,特别要注意的是 output_buffers、proxy_buffers 和 ssl_buffer_size 等指令,多测试一下。参见 general configuration notes,特定的 SSL 建议,以及 NGINX 关于 SSL 性能的白皮书。
注意:使用 HTTP/2 传输密文要格外注意。HTTP/2 的 RFC 中有一个长长的列表,列出了要避免的加密套件。建议你自己也搞一个表格,启用 ssl_buffer_size,然后在所有常用的浏览器版本下测试你想用的加密套件。
建议五:再谈 HTTP/1.x 优化
你说奇怪不,撤销和修改针对 HTTP/1.x 优化的代码居然是实现 HTTP/2 最有创意的部分。这里面有几个问题要注意,因为很多事怎么做都是可以的。
在开始运作之前,必须考虑旧版本浏览器用户是否好过。之后,可以采取三个策略撤销和修改 HTTP/1.x 的优化。
- 什么也不用做。假如你并没有针对 HTTP/1.x 做过优化,或者只做过少量优化,那么你几乎什么也不用做,就可以直接迁移到 HTTP/2。
- 有选择地去做。第二种情况是减少合并某些文件,而不是完全不合并。比如,牵扯到很多场景的雪碧图就不用动,而被塞得满满的 HTML 可能就要分离出来一些。
- 完全撤销 HTTP/1.x 优化。可以不再做以前做过的任何优化。
缓存还是普适的。理论上,缓存操作非常适合小文件特别多的情况。但是,小文件多也意味着文件 I/O 多。因此一些相近文件的合并还是必要的,一方面要考虑工作流程,另一方面要考虑应用性能。建议多关注一下其他人在过渡到 HTTP/2 过程中的一些经验。
建议六:实现智能分域
分域存储可能是最极端但也最成功的 HTTP/1.x 优化策略。它能够提升 HTTP/1.x 下的应用性能,但在 HTTP/2 之下,其性能提升可以忽略不讲(因为只有一个连接。)
对 HTTP/2 友好的分域,要保证以下两点:
- 让多个域名解析到同一个 IP。
- 确保证书包含通配符,以便所有分域名都可以使用,适当的多域证书当然也可以。
有了这些保障,分域还会继续对 HTTP/1.x 有效,即域名仍然可以触发浏览器创建更多连接,但对 HTTP/2 则无效,因为这些域名会被看成同一个域,一个连接就可以访问所有域名了。
HTTP/2 协议客户端和服务支持情况
客户端
PC 端
如果业务提供的是 web 形式的内容,通过浏览器进行访问,由于当前大部分的浏览器都已经支持 HTTP/2 了,所以基本不需进行任何操作,以下为支持 HTTP/2 的浏览器列表:
浏览器 | 支持HTTP/2 | 基于的内核 | 备注 | |
---|---|---|---|---|
Chrome(49) | 支持 | 从49版本开始支持 | ||
IE 11 | 不支持 | win10系统上的IE11支持h2 | ||
Edge(14) | 支持 | 从14版本开始支持 | ||
Safari(10.1) | 支持 | 从10.1版本开始支持,但都需要OSX10.11+以上系统版本 | ||
Firefox(52) | 支持 | 从52版本开始支持 | ||
Opera(47.0.2631.55) | 支持 | 从46版本开始支持 | ||
搜狗浏览器(7.1.5.25209) | 支持 | |||
猎豹浏览器(6.0.114.15532) | 支持 | |||
2345加速浏览器(8.7.0.16013) | 支持 | |||
百度浏览器(8.7.5000.4962) | 不支持 | chrome 47 | ||
QQ浏览器(9.6.4) | 支持 | chrome 53 | ||
360浏览器(9.1.0.346) | 支持 | chrome 55 | ||
360极速浏览器(8.7.0.306) | 支持 |
移动端
安卓 app 基本采用 JAVA 开发,由于各个应用采用的与服务端通信的 http 库各不相同,有的是采用 jdk 自带的 httpurlconnection 库和 httpclient 库,有的用的是安卓系统自带的 webview 或者 volley(volley 的 HTTP/2 支持依赖于所使用的 httpstack,默认使用 httpurlconection,但现在也有很多开发者使用第三方的 okhttp 作为 volley 的 httpstack),而有的开发者直接使用的是第三方库类似于 okhttp,netty 等,这些 http 库对 http2.0 的支持情况各不相同。
基于 java 开发的 http 库对 HTTP/2 的支持情况如下:
- Jetty 从 9.3 版本开始支持,需要依赖于 JDK 8 及以上版本
- Netty 从 4.1 版本开始支持
- OkHttp 天然支持
- Vert.xh 从 3.3.0 版本开始支持
- Firefly 支持
其它
Golang 的 net/http 库从 Go1.6 版本开始支持 http2,并默认开启
服务端
名称 | 支持的版本 | 支持的协商机制 | |
---|---|---|---|
Apache HTTP Server 2.4.17+ | h2,h2c | ALPN,Upgrade,direct | |
Apache Tomcat 8.5+ | h2,h2c | ALPN,Upgrade,direct | |
Nginx | h2,h2c | ALPN,NPN,direct | |
Tengine 2.1.2+ | h2 | ALPN | |
Twisted | h2 | NPN,ALPN | |
Netty | h2,h2c | ALPN,NPN,Upgrade,direct |
在 HTTP/2 的 github 中维护了一份 HTTP/2 协议的实现列表,更加详细,可供参考。
扩展阅读
1.NPN 和 ALPN
由于现有的 URI 结构正在被 HTTP 1.x 使用而不能被更换,所以 HTTP/2 也必须沿用该结构。因此不得不找到一种方式将使用的协议升级至 HTTP/2,比如可以要求服务器让它作响应时使用 HTTP/2 来替代旧的协议。
HTTP 1.1 本身就制定过 “升级” 的方案:提供一个首部字段,表示允许服务器在收到旧协议请求的同时,可以向客户端发送新协议的响应。但这一方案往往需要花费一次额外的往返通信来作为升级的代价。
而这一代价是 SPDY 团队不想接受的。因为他们只实现了基于 TLS 的 SPDY,所以他们开发了一个 TLS 的扩展去简化协议的协商。这个扩展被称作 NPN(Next Protocol Negotiation),借助于此,服务器会通知客户端所有它支持的协议,让客户端从中选择一个合适的来进行通讯。
IETF 将这个非正式标准 --NPN 进行规范化,从而演变成了 ALPN(Application Layer Protocol Negotiation)。ALPN 会随着 HTTP/2 的应用被推广,而 SPDY 的客户端与服务器则会继续使用 NPN。
由于 NPN 先于 ALPN 诞生,而 ALPN 又经历了一些标准化过程,所以许多早期的 HTTP/2 客户端和服务器在协商 HTTP/2 时会将这两者同时实现。与此同时,考虑到 SPDY 会使用 NPN,而许多服务器又会同时提供 SPDY 以及 HTTP/2,所以在这些服务器上同时支持 ALPN 以及 NPN 显然会成为最理所当然的选择。
ALPN 和 NPN 的主要区别在于:谁来决定通信协议。在 ALPN 的描述中,是让客户端先发送一个协议优先级列表给服务器,由服务器最终选择一个合适的。而 NPN 则正好相反,客户端有着最终的决定权。
ALPN 扩展的具体资料,可以参考 Jerry Qu 写的这篇博客:谈谈 HTTP/2 的协议协商机制
2.QUIC
QUIC (Quick UDP Internet Connection,快速 UDP 互联网连接) 是一个新的基于 UDP 的多路复用且安全的传输协议,它从头开始设计,且为 HTTP/2 语义做了优化。尽管以 HTTP/2 作为主要的应用协议而构建,然而 QUIC 的构建是基于传输和安全领域数十年的经验的,且实现了使它成为有吸引力的现代通用传输协议的机制。QUIC 提供了等价于
HTTP/2 的多路复用和流控,等价于 TLS 的安全机制,及等价于 TCP 的连接语义、可靠性和拥塞控制。
QUIC 完全运行于用户空间,它当前作为 Chromium 浏览器的一部分发布给用户,以便于快速的部署和实验。作为基于 UDP 的用户空间传输协议,QUIC 可以做一些由于遗留的客户端和中间设备,或旷日持久的操作系统开发和部署周期的阻碍,而被证明很难在现有的协议中部署的创新。
QUIC 的一个重要目标是通过快速的实验获得更好的传输设计相关的知识。
基于早期的部署的 QUIC 标准化建议为 [draft-hamilton-quic-transport-protocol],[draft-shade-quic-http2-mapping],[draft-iyengar-quic-loss-recovery],和 [draft-thomson-quic-tls]。
更加详细的资料请参考这里:中文文档; Chromium 的 QUIC 主页。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。