HTTP2.0
HTTP2为了解决HTTP1.1中存在的问题。其中慢启动和TCP连接竞争是TCP本身导致的,在H2中依赖的还是TCP协议,不过思路换了一下。
HTTP/2 的思路就是一个域名只使用一个 TCP 长连接来传输数据,这样整个页面资源的下载过程只需要一次慢启动,同时也避免了多个 TCP 连接竞争带宽所带来的问题。
还有就是队头阻塞的问题,原本是需要等待请求完成后才能去请求下一个资源,这种方式无疑是最慢的,所以 HTTP/2 需要实现资源的并行请求,也就是任何时候都可以将请求发送给服务器,而并不需要等待其他请求的完成,然后服务器也可以随时返回处理好的请求资源给浏览器。
下面我们就来看看在H2中具体是如何实现的以及H2的一些新特性。
二进制分帧层
HTTP/2所有性能增强的核心是新的二进制分帧层,它规定了HTTP消息如何在客户机和服务器之间封装和传输。如下图所示:
新的二进制帧机制的引入改变了客户端和服务器之间的数据交换方式。为了描述这个过程,先熟悉一下HTTP/2术语:
- 流:存在于连接中的一个虚拟通道,一个已建立的连接中的双向字节流,它可以携带一条或多条消息,每个流都有一个唯一的整数 ID
- 消息:映射到逻辑请求或响应消息的完整帧序列
- 帧(frame):HTTP/2中最小的通信单元,每个都包含一个帧报头,它至少标识了帧所属的流。根据上图我们可以知道,每一帧把数据分成两大部分即 header frame 和 data frame。也就是头部帧和数据体帧
概述这些项之间的关系为:
- 所有通信都在一个TCP连接上执行,该连接可以携带任意数量的双向流
- 每个流都有一个唯一的标识符和可选的优先级信息,用于携带双向消息
- 每个消息都是一个逻辑HTTP消息,例如请求或响应,由一个或多个帧组成
- 帧是传输特定类型数据的最小通信单元。、HTTP报头、消息有效负载等等。来自不同流的帧可以交叉,然后通过每个帧头中的嵌入流标识符重新组合
基于上面的概念,我们来看下一个H2请求的具体的请求和接受过程:
- 浏览器准备好请求数据,包括了请求头和请求体(如果是 POST 请求)
- 数据经过二进制分帧层处理之后,会被转换为一个个带有请求 ID 编号的帧,通过协议栈将这些帧发送给服务器
- 服务器接收到所有帧之后,会将所有相同 ID 的帧合并为一条完整的请求信息
- 然后服务器处理该条请求,并将处理的响应头和响应体分别发送至二进制分帧层
- 二进制分帧层会将这些响应数据转换为一个个带有请求 ID 编号的帧,经过协议栈发送给浏览器
- 浏览器接收到响应帧之后,会根据 ID 编号将帧的数据提交给对应的请求
示意图如下所示:
简而言之,HTTP/2将HTTP协议通信分解为二进制编码帧的交换,然后将其映射到属于特定流的消息,所有这些消息都在单个TCP连接中进行多路复用。这是启用HTTP/2协议提供的所有其他特性和性能优化的基础。
此外HTTP2 采用二进制数据帧传输,取代了 HTTP1.x 的文本格式,二进制格式解析更高效。
多路复用
先看一下在HTTP不同阶段的不同请求模型:
在HTTP1.1中要想发出多个并行的请求来提高网页性能,那就必须使用多个TCP连接(单个域名最多同时维护6个TCP链接)。这也是导致TCP之间竞争的直接原因,并且也导致了队头阻塞。
在H2中引入新的二进制分帧层之后就消除了这些限制,允许客户端和服务端将HTTP消息分解为独立的帧,传送时可以相互交叉,会根据标记字段在另一端进行重新组合,从而实现了完整的请求和多路复用。
如下如所示:
上图显示了在一个连接中正在运行的多个流。客户端向服务器发送数据帧(流5),而服务器则向客户端发送帧的交错序列,用于流1和流3。在该连接中同时又三个不同的流在传递信息。
将HTTP请求分解成独立的帧,交叉传递,然后在另一端重新组合,使用这样的方式可以让我们获得以下收益:
- 并行地交叉多个请求而不阻塞任何一个
- 平行交错多个响应而不阻塞任何一个
- 使用单个连接并行地交付多个请求和响应
- 通过消除不必要的延迟和提高可用网络容量的利用率,降低页面加载时间,比如避免了过多的TCP慢启动,以及TCP的竞争
流的优先级
一旦HTTP消息可以被分割成许多单独的帧,并且允许来自多个流的帧进行多路复用,帧的交错顺序和由客户端和服务器传递的顺序就成为关键的性能考虑因素。实际上HTTP/2标准允许每个流有一个相关的权重和依赖:
- 每个流可以被分配1到256之间的整数权重
- 每个流都可以显式地依赖于另一个流
流依赖关系和权重的组合允许客户端构建并传递一个“优先级树”,该树表示它希望如何接收响应。反过来,服务器可以使用此信息通过控制CPU、内存和其他资源的分配来确定流处理的优先级,并且一旦响应数据可用,就可以分配带宽以确保向客户端最佳地交付高优先级响应,如下图所示:
HTTP/2中的流依赖是通过引用另一个流的唯一标识符作为父流来声明的;如果省略了标识符,则称流依赖于“根流”。声明流依赖项表明,如果可能的话,父流应该在其依赖项之前分配资源。举个例子,“请在响应C之前处理并交付响应D”
兄弟流应该按其权重的比例分配资源。例如,如果流A的权重为12,而它的兄弟流B的权重为4,那么要确定每个流应该接收的资源的比例
- 所有的权重:12 + 4 = 16
- 每个流的权重:A = 12/16, B = 4/16
因此,流A应该接收四分之三的可用资源,流B应该接收四分之一的可用资源。
正对上述图中的一些实际例子资源的分配应该按照这样来(从做到右):
- 流A和流B都没有指定父依赖项,被认为依赖于隐式的“根流”;A的权重是12,B的权重是4。因此,根据比例权重:流B应该获得分配给流A的资源的三分之一。
- 流D依赖于根流;C依赖于D,因此D应该在C之前得到充分的资源分配。权重是无关紧要的
- 流D应该在C之前得到充分的资源分配;C应先于A和B获得充分的资源分配;流B应该获得分配给流A的资源的三分之一
- 流D应先于E和C获得充分的资源分配;E和C应该在A和B之前得到相等的分配;A和B应根据其权重按比例分配
通过上述的依赖关系和权重组合就为资源的优先级提供了一种表达方式。
服务器推送
HTTP/2的另一个强大的新特性是服务器能够为单个客户端请求发送多个响应。也就是说,除了对原始请求的响应之外,服务器还可以向客户端推送额外的资源,而不需要客户端明确地请求每个资源。如下图所示:
HTTP/2摆脱了严格的请求-响应语义,支持一对多和服务器发起的推送工作流。
有了这种机制,当我们在一个大型的web应用程序中可能会包含几十个资源组成,所有的资源都是客户端通过解析服务端提供的index.html文档来发现的,因此,可以让服务器提前推送相关的资源,避免额外的延迟。
头压缩
每个HTTP传输都带有一组头,描述传输的资源及其属性。在HTTP / 1.x,此元数据总是以纯文本形式发送,每次传输会增加500-800字节的开销,如果使用HTTP cookie,有时还会增加几千字节。为了减少这种开销并提高性能,HTTP/2使用HPACK压缩格式压缩请求和响应头元数据。
可能你觉得一个 HTTP 的头文件没有多大,压不压缩可能关系不大,但想一下,在浏览器发送请求的时候,基本上都是发送 HTTP 请求头,很少有请求体的发送,通常情况下页面也有 100 个左右的资源,如果将这 100 个请求头的数据压缩为原来的 20%,那么传输效率肯定能得到大幅提升
总结
在http1.x时代遗留的三个问题。
- TCP慢启动
- 多条TCP连接竞争带宽
- 队头阻塞
这些问题在H2中是如何通过多路复用的方式解决的。
多路复用是通过在协议栈中添加二进制分帧层来实现的,有了二进制分帧层还能够实现请求的优先级、服务器推送、头部压缩,此外HTTP2 采用二进制数据帧传输,取代了 HTTP1.x 的文本格式,二进制格式解析更高效。
据统计使用 HTTP/2 能带来 20%~60% 的效率提升,至于 20% 还是 60% 要看优化的程度了。
H2的优点:
- 多路复用
- 控制优先级
- 服务端主动推送
- 头部压缩
- 二进制帧代替H1.x文本格式,解析效率更高
参考文章
Introduction to HTTP/2