Web技术(五):HTTP/2 是如何解决HTTP/1.1 性能瓶颈的?

一、HTTP/2 概览

前篇博文:图解HTTP中介绍了HTTP的演进史,同时也提到了HTTP/1.1 版本存在的性能瓶颈,比如服务器因为只能逐个顺序响应HTTP请求可能引起的队头阻塞问题,因为每个HTTP 报文都要传输臃肿的首部字段导致的网络效率降低等问题。

解决上面的问题也有相应的思路,比如HTTP 响应的队头阻塞问题可以让多个请求/响应报文并发复用一个TCP连接,不至于因为某个请求报文响应阻塞而影响后续所有请求报文的响应。HTTP报文重复传输臃肿的首部字段的问题,前篇博文也给出了相应的解决思路,通信双方可以都维护一张HTTP 首部字段索引列表,报文中只传输对应字段的索引值,就能大大压缩报文首部的长度,提高网络利用率。

2009年,Google提出了一种HTTP/1.1 的替代方案SPDY (发音同Speedy)来改善HTTP/1.1 遗留的性能瓶颈问题,经过几年的验证改进后,SPDY 带来了显著的网络访问效率提升。因其出色的性能,SPDY 成为HTTP/2 开发的基础,并证明了前面提出的一些解决思路的合理性,比如TCP连接多路复用、二进制编码和首部压缩等。

2012 年,HTTP 工作组(IETF 工作组中负责HTTP 规范的小组)启动了开发下一个HTTP 版本的工作,并最终决定使用SPDY 作为HTTP/2.0 的起点。2015年,HTTP/2 作为正式协议发布于RFC7540
HTTP/2概览
HTTP/2 是一个彻彻底底的二进制协议,头信息和数据包体都是二进制的,统称为“帧”。对比HTTP/1.1 中,头信息是文本编码(ASCII编码),数据包体可以是二进制也可以是文本。HTTP/2 使用二进制作为协议实现方式的好处是便于计算机直接处理,但不方便我们肉眼识别。

HTTP/2 中的基本协议单元是一个帧,在 HTTP/2 中定义了 10 种不同类型的帧,每种帧类型都有不同的用途。比如 HEADERS 和 DATA 帧构成了 HTTP 请求和响应的基础;其它帧类型(比如 PRIORITY、SETTINGS、PUSH_PROMISE、WINDOW_UPDATE 等 )用于支持其它 HTTP/2 功能。
HTTP/2多路复用流
HTTP/1.1 中由于服务器只能逐个顺序响应请求报文,前一个响应未完成就只能一直阻塞等待而无法传输下一个响应报文,这就白白浪费了TCP 连接的带宽资源,同时带来了队头阻塞问题。HTTP/2 支持多个资源请求/响应报文在同一个TCP连接上并发传输,可以充分利用TCP连接的带宽资源,也不会因为某个报文响应一直阻塞等待而影响其它报文的传输,同时解决了队头阻塞问题。

HTTP/2 中不同资源的数据包在同一个TCP连接上是乱序传输的,怎么判断哪些数据包是同一个资源的呢?怎么将收到的数据包正确组合成目标资源呢?这就要求每个数据包都包含相应的标识信息,用来标识它属于哪个资源或者哪个请求/响应报文。

HTTP/2 把每个 request 和 response 的数据包称为一个数据流(stream),每个数据流都有自己全局唯一的编号,每个数据包在传输过程中都需要标记它属于哪个数据流 ID。规定,客户端发出的数据流 ID 一律为奇数,服务器发出的数据流 ID 一律为偶数。
HTTP/2首部压缩
HTTP/2 在客户端与服务器端都维护了一张首部字段索引列表, header 字段列表是以key - value 键值对元素构成的有序集合,每个header 字段元素都映射为一个索引值,报文中使用header 字段的索引值进行二进制编码传输,显然比HTTP/1.1 直接使用header 字段ASCII 编码传输,数据量小得多,这种减少header 字段传输开销的技术可以称为首部压缩HPACK。

为了保证header 字段的索引值能正确解码,客户端与服务器端的header 字段列表索引映射关系应该完全一致。为了进一步降低header 字段的传输开销,这些 header 字段表可以在编码或解码新 header 字段时进行增量更新,新的header 字段采用Huffman 编码(摩斯电码就采用了霍夫曼编码)可以进一步降低编码后的字节数。

再回顾下 HTTP/2 解决了HTTP/1.1 的哪些性能瓶颈或者带来了哪些性能提升:

HTTP/1.1 性能瓶颈 HTTP/2 改进优化
存在队头阻塞问题,降低了TCP连接利用率 通过多数据流并发复用TCP连接,不仅解决了队头阻塞问题,还大大提高了TCP连接的利用率
重复传输臃肿的首部字段,降低了网络资源利用率 通过首部压缩,大大减少了需要传输的首部字段字节数,进一步提高了网络资源利用率
报文各字段长度不固定,增加了报文解析难度,只能串行解析 整个报文都采用二进制编码,且每个字段长度固定,可以并行处理,提高了报文处理效率
只能客户端发起请求,服务器响应请求,服务器端的数据更新不能及时反馈给客户端 支持服务器端向客户端推送资源,服务器端的数据更新可以及时反馈给客户端,也可以通过预判客户端需求提前向客户端推送相应资源,提高客户端的访问响应效率

二、HTTP/2 协议原理

HTTP/2 是为了解决HTTP/1.1 的性能瓶颈问题诞生的,在提供更高网络访问效率的同时,自然需要向前兼容HTTP/1.1。HTTP/2 和HTTP/1.1 使用相同的 “http” 和 “https” URI scheme,共享相同的默认端口号: “http” URI 为 80,“https” URI 为 443。既然从URL 上无法区分HTTP/2 和HTTP/1.1,我们只能从报文标头信息辨识网站使用的是哪个协议版本了(比如报文中的“协议版本”字段)。

前面已经简单介绍过HTTP/2 相比HTTP/1.1 比较重要的几个特性:二进制帧层、多数据流并发、Header 压缩、服务端推送等,下面分别对其介绍。

2.1 Binary frame layer

前面介绍了,HTTP/2 是基于帧 (frame)的协议,采用分帧是为了将重要信息都封装起来,让协议的解析方可以轻松阅读、解析并还原信息。 相比之下,HTTP/1.x 不是基于帧的,而是以文本分隔。 解析这种文本分割的数据往往速度慢且容易出错,你需要不断读入字节,直到遇到分隔符为止(这里指[CRLF]),这会带来以下弊端:

  • 一次只能处理一个请求或响应,完成之前不能停止解析,只能串行解析报文;
  • 无法预判解析需要多少内存。这会带来一系列问题:你要把一行读到多大的缓冲区里;如果行太长而缓冲区不够用,应该增加并重新分配内存还是返回400 错误;这些问题显然会降低内存处理的效率和速度。

HTTP/2 使用帧来封装各字段信息,帧中包含表示整帧长度的字段,每个字段也有固定的长度,处理帧协议的程序就能预先知道会收到哪些字段信息,每个字段占用多少内存空间,也就可以并行处理数据帧。HTTP/2 的帧结构如下图示:
HTTP/2帧结构
前9 个字节对于每个帧都是一致的,解析时只需要读取这些字节,就可以准确地知道在整个帧中期望的字节数。帧首部各字段说明见下表:

帧字段名称 长度 描述
Length 3字节 表示帧负载的长度(取值范围为214 ~ 224-1 字节),
值得注意的是,214 字节是默认的最大帧大小,
如果需要更大的帧,可在SETTINGS 帧中设置
Type 1字节 当前帧类型
Flags 1字节 具体帧类型的标识,不同的帧类型标识不一样
R 1位 保留位,不要设置,否则可能带来严重后果
Stream Identifier 31位 每个流的唯一ID
Frame Payload 长度可变 真实的帧内容,长度是在Length 字段中设置的

如果读者对TCP/IP 协议栈比较了解的话,会发现HTTP/2 的帧结构与TCP/UDP/IP数据报文格式有很大的相似之处,报文中都包含了总长度字段信息,每个字段的长度都是固定的,计算机处理这种报文/帧会更加简单高效。

得益于帧结构的优势,HTTP/2 可以并行处理多个数据帧,再借助Stream Identifier 字段标识每个请求/响应数据流,可以让不同数据流的数据帧交错的在TCP连接上传输(借助Stream ID,即便交错传输也可以重新组装),这就实现了多个数据流并发复用同一个TCP连接的效果。

多数据流并发复用同一个TCP连接,有点类似于多线程并发复用同一个处理器资源,不会因为某一个数据流的阻塞等待而影响其它数据流的传输,既提高了TCP连接的利用效率,又解决了HTTP/1.1 中的队头阻塞问题。可以说,HTTP/2 帧结构是数据流多路复用的基础,也是解决队头阻塞问题的关键。

HTTP/2 中定义了 10种不同的帧类型,每种帧类型都有不同的用途,其中 HEADERS 和 DATA 帧构成了 HTTP 请求和响应的基础,这十种帧类型及功能描述如下:

帧类型名称 ID 描述
DATA 0x0 传输流的核心内容
HEADERS 0x1 包含HTTP 首部,和可选的优先级参数
PRIORITY 0x2 指示或者更改流的优先级和依赖
RST_STREAM 0x3 允许一端停止流(通常由于错误导致的)
SETTINGS 0x4 协商连接级参数
PUSH_PROMISE 0x5 提示客户端,服务器要推送些东西
PING 0x6 测试连接可用性和往返时延(RTT)
GOAWAY 0x7 告诉另一端,当前端已结束
WINDOW_UPDATE 0x8 协商一端将要接收多少字节(用于流量控制)
CONTINUATION 0x9 用以扩展HEADER 数据块

可以说,HTTP/2 所有性能增强的核心在于新的二进制分帧层,它定义了如何封装 HTTP 消息并在客户端与服务器之间传输。这里所谓的“层”,指的是位于套接字接口与应用可见的高级 HTTP API 之间一个经过优化的新编码机制:HTTP 的语义(包括各种动词、方法、标头)都不受影响,不同的是传输期间对它们的编码方式变了。 HTTP/1.x 协议以换行符作为纯文本的分隔符,而 HTTP/2 将所有传输的信息分割为更小的消息和帧,并采用二进制格式对它们编码。
HTTP/2数据帧传输请求响应报文
限于篇幅,没法对这十种类型的帧定义进行全面介绍,下面选择构成HTTP/2 请求响应基础的HEADERS 帧和 DATA 帧为例,简单介绍完整的帧定义格式。

2.1.1 DATA帧定义

DATA帧(类型Type = 0x0)比较简单,主要是用来封装报文主体数据的,DATA帧结构定义如下(HTTP/2帧首部定义前面介绍过了,这里的DATA帧结构属于HTTP/2帧结构中的Frame Payload部分,其余类型帧结构定义也是如此):
DATA帧结构
DATA 类型的帧包含的字节长度不定,如果超出帧容许的最大长度,资源数据会被切分到一个或者多个帧里面去。上图中的填充长度(Pad Length)字段和填充数据(Padding)属于可选字段,可以借助填充数据隐藏真实的消息大小(出于安全方面的考虑)。DATA帧字段及对应的帧类型标识如下:

DATA帧标识位Flags名称 描述
END_STREAM 0x1 表明这是流中最后的帧(流终止)
PADDED 0x8 表明此帧添加了填充数据,要处理Pad Length 和Padding 字段
DATA帧字段名称 长度 描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流云IoT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值