HTTP2的特性解析

本文主要介绍了HTTP2相对于HTTP1.1的一些改进和新特性。

1、HTTP1.1的不足

HTTP协议采用“request-response”模式,当使用普通模式,即非KeepAlive模式时,每个请求/应答客户和服务器都要新建一个连接,完成之后立即断开连接(HTTP协议为无连接的协议)。对于一个复杂的页面,一般都会有多个资源需要获取。如果是每个资源都单独建立一个TCP连接去获取,每次都需要进行三次握手建立连接来通信,这是十分耗费资源的。

随后出现了Keep-AliveKeep-Alive解决的核心问题是一定时间内,同一域名多次请求数据,只建立一次HTTP请求,其他请求可复用每一次建立的连接通道,以达到提高请求效率的问题。这里面所说的一定时间一般是可以自行配置的。当使用Keep-Alive模式(又称持久连接、连接重用)时,Keep-Alive功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接。

HTTP1.1中是默认开启了Keep-Alive,虽然解决了多次连接的问题,但是依然有两个比较严重的问题:

  • 串行传输:在单通道传输的前提下,假设要传输10个文件,那么只能依次逐个传输,传输完第一个再传输第二个,以此类推;
  • 连接数问题:HTTP/1.1 虽然默认开启keep-alive可以复用一部分连接,但域名分片等情况下仍然需要建立多个connection,耗费资源,给服务器带来性能压力。

作为 HTTP/1.x 的连接,请求是序列化的,哪怕本来是无序的,在没有足够庞大可用的带宽时,也无从优化。一个解决方案是,浏览器为每个域名建立多个连接,以实现并发请求。曾经默认的连接数量为 2 到 3 个,现在比较常用的并发连接数已经增加到 6 条。如果尝试大于这个数字,就有触发服务器 DoS 保护的风险。如果服务器端想要更快速的响应网站或应用程序的应答,它可以迫使客户端建立更多的连接。例如,不要在同一个域名下获取所有资源,假设有个域名是 www.example.com,我们可以把它拆分成好几个域名:www1.example.comwww2.example.comwww3.example.com。所有这些域名都指向同一台服务器,浏览器会同时为每个域名建立 6 条连接(在我们这个例子中,连接数会达到 18 条)。这一技术被称作域名分片

HTTP1.1中的数据传输是基于文本来进行传输的,这就需要保证在传输的过程中必须是按照文本原有的顺序进行传输。按照顺序传输的一大弊端就是没办法进行并行传输,例如一句Hello就只能按照字母顺序逐个传输,如果并行传输,字母到达的先后顺序是不一定和在文本中的顺序一样的,而字母本身又没有顺序标号,就没有办法对其进行排序,很有可能就会导致内容错乱,Hello就可能变成leloH

2、HTTP2的特性

2.1 二进制分帧

http2采用二进制格式传输数据,而非 HTTP 1.x 的文本格式,二进制协议解析起来更高效。 HTTP / 1 的请求和响应报文,都是由起始行,首部和实体正文(可选)组成,各部分之间以文本换行符分隔。HTTP/2 将请求和响应数据分割为更小的帧,并且它们采用二进制编码。HTTP/2 中,同域名下所有通信都在单个连接上完成,该连接可以承载任意数量的双向数据流。每个数据流都以消息的形式发送,而消息又由一个或多个帧组成。多个帧之间可以乱序发送,根据帧首部的流标识可以重新组装。

  • 流:流是连接中的一个虚拟信道,可以承载双向的消息;每个流都有一个唯一的整数标识符(1、2…N);
  • 消息:是指逻辑上的 HTTP 消息,比如请求、响应等,由一或多个帧组成。
  • 帧:HTTP 2.0 通信的最小单位,每个帧包含帧首部,至少也会标识出当前帧所属的流,承载着特定类型的数据,如 HTTP 首部、负荷,等等

HTTP/2引入的二进制数据帧的概念,其中帧对数据进行顺序标识,这样浏览器收到数据之后,就可以按照序列对数据进行合并,而不会出现合并后数据错乱的情况。同样是因为有了序列,服务器就可以并行的传输数据,这就是所做的事情。

我们可以从下面的两张图来对其进行了解:

多路复用 (multiplexing)

在HTTP/2 协议中, ConnectionKeep-Alive 是被忽略的;主要使用了多路复用来进行连接管理。在 HTTP/2 中,有了二进制分帧之后,HTTP /2 不再依赖 TCP 连接去实现多流并行了,在 HTTP/2 中

  • 同域名下所有通信都在单个连接上完成
  • 单个连接可以承载任意数量的双向数据流
  • 数据流以消息的形式发送,而消息又由一个或多个帧组成,多个帧之间可以乱序发送,因为根据帧首部的流标识可以重新组装。

这一特性,使性能有了极大提升:

  • 同个域名只需要占用一个 TCP 连接,使用一个连接并行发送多个请求和响应,消除了因多个 TCP 连接而带来的延时和内存消耗
  • 并行交错地发送多个请求和响应,两两之间互不影响
  • 在 HTTP/2 中,每个请求都可以带一个 31bit 的优先值,0 表示最高优先级, 数值越大优先级越低。有了这个优先值,客户端和服务器就可以在处理不同的流时采取不同的策略,以最优的方式发送流、消息和帧。

使用浏览器的开发者模式查看相关资源的加载情况,可以看到对应的http2的资源在加载的时候会使用多路复用,客户端可以同时请求多个资源;而下方的http1.1则在请求资源的时候出现了等待的情况。

2.2 首部压缩(header compression)

在同一个HTTP页面中,许多资源的Header是高度相似的,但是在HTTP2之前都是不会对其进行压缩的,这使得在多次传输中白白浪费了资源来进行重复无谓的操作。

在HTTP中,首部字段是一个键值对,所有的首部字段组成首部字段列表。在HTTP/1.x中,首部字段都被表示为字符串,一行一行的首部字段字符串组成首部字段列表。而在HTTP/2的首部压缩HPACK算法中,则有着不同的表示方法。

HPACK算法的具体细节可以参考RFC7541

HPACK算法表示的对象,主要有原始数据类型的整型值和字符串,头部字段,以及头部字段列表。

头部压缩需要在支持 HTTP/2 的浏览器和服务端之间:

  • 维护一份相同的静态字典(Static Table),包含常见的头部名称,以及特别常见的头部名称与值的组合。其主要作用有两个:对于完全匹配的头部键值对,例如 :method: GET,可以直接使用一个字符表示;对于头部名称可以匹配的键值对,例如 cookie: xxxxxxx,可以将名称cookie使用一个字符表示。
  • 维护一份相同的动态字典(Dynamic Table),可以动态地添加内容。对于cookie: xxxxxxx 这样的内容,可以将其添加到动态字典中,之后整个键值对只使用字段中替代的字符来表示即可。需要注意的是,动态字典上下文有关,需要为每个 HTTP/2 连接维护不同的字典。
  • 支持基于静态哈夫曼码表的哈夫曼编码(Huffman Coding);

使用字典可以极大地提升压缩效果,其中静态字典在首次请求中就可以使用。对于静态、动态字典中不存在的内容,还可以使用哈夫曼编码来减小体积。HTTP/2 使用了一份静态哈夫曼码表,也需要内置在客户端和服务端之中。

下面的表格截取了部分静态字典的内容:

2.3 服务器推送(server push)

Server Push 即服务端能通过 push 的方式将客户端需要的内容预先推送过去,也叫“cache push”。这个新特性是有些颠覆了传统的HTTP模式的,因为传统的HTTP模式是客户端请求(request)资源,然后服务器才会返回(response)对应的资源,而Server Push的方式是服务器主动将资源推送到客户端。

可以想象以下情况,某些资源客户端是一定会请求的,这时就可以采取服务端 push 的技术,提前给客户端推送必要的资源,这样就可以相对减少一点延迟时间。例如服务端可以主动把 JS 和 CSS 文件推送给客户端,而不需要客户端解析 HTML 时再发送这些请求。

服务端可以主动推送,客户端也有权利选择是否接收。如果服务端推送的资源已经被浏览器缓存过,浏览器可以通过发送 RST_STREAM 帧来拒收。实际上服务器应该避免推送已经被客户端缓存了的资源,一般可以通过判断请求中的缓存相关字段来确定是否需要推送。主动推送也遵守同源策略,换句话说,服务器不能随便将第三方资源推送给客户端,而必须是经过双方确认才行。

还可以使用server push可以提前将需要加载的资源推送到客户端,这样还可以有效提升页面的加载速度:

从上面两张图片的对比中我们可以看到一个大小为50KB左右的main.css文件在使用了服务器推送功能之后的加载时间从原来的73.98ms变为19.54ms

3、HTTP2协商机制

由于目前并不是所有的客户端和服务器都支持HTTP2,因此在建立HTTP连接的过程中必然会存在一个协商的过程,具体的协商流程如下:

4、HTTP2的不足

HTTP2在我个人看来在协议的设计方面应该已经是发挥了目前的全部性能,除非有什么惊人的压缩算法之类的出现,否则很难再有极大的改进(实际上HTTP2对比HTTP1.1在实际应用上的性能提升也并不算特别大)。但是由于HTTP协议的传输层还是使用的TCP,这就导致了可能会出现TCP的一些问题。

例如在多路复用的情况下, 一般来说同一域名下只需要使用一个 TCP 连接。但当这个连接中出现了丢包的情况,TCP的特性会需要重新传输,也就是需要把所有的请求都重新传输一次,那就会导致 HTTP/2 的表现情况反倒不如 HTTP/1.1 了。因为在出现丢包的情况下,整个 TCP 都要开始等待重传,也就导致了后面的所有数据都被阻塞了。但是对于 HTTP/1.1 来说,可以开启多个 TCP 连接,出现这种情况反到只会影响其中一个连接,剩余的 TCP 连接还可以正常传输数据。这种情况和队头堵塞情况有些类似,但是HTTP2的这种情况一般会出现在网络较差的情况下,也就是说HTTP2会有一种快的更快,慢的更慢的倾向。

Head-Of-Line Blocking(HOLB):导致带宽无法被充分利用,以及后续健康请求被阻塞。HOLB
是指一系列包(package)因为第一个包被阻塞;当页面中需要请求很多资源的时候,HOLB(队头阻塞)会导致在达到最大请求数量时,剩余的资源需要等待其他资源请求完成后才能发起请求。