articles icon indicating copy to clipboard operation
articles copied to clipboard

技术 / 网络 / HTTP2.0总结

Open huanzhiyazi opened this issue 3 years ago • 0 comments

目录

  • 1 HTTP2.0的设计目标
  • 2 HTTP2.0的核心组成
  • 3 通信格式的优化——帧、消息、数据流
  • 4 TCP连接多路复用
  • 5 数据流优先级处理
  • 6 每个源一个连接
  • 7 流量控制
  • 8 服务器推送
  • 9 头部压缩技术


1 HTTP2.0的设计目标[Top]

HTTP2.0 的前身是由谷歌主导的 SPDY 协议,针对 HTTP1.x 的问题,SPDY 在立项之初设定了如下几个技术目标:

  • 减少 50% 的页面加载时间(PLT)。

  • 避免让 web 站点的所有者修改任何 web 上内容。

  • 最小化新协议的部署成本,避免对网络架构进行修改。

  • 开源。

  • 采集真实网络的性能数据来不断修正协议。

总结其核心的目标其实就两个:

  1. 加速。

  2. 一键加速。

加速,意味着要比 HTTP1.x 更快;一键加速,意味着可以在不修改上层应用和网络框架的基础上简单快速地部署。这说明,SPDY/HTTP2.0 是对 HTTP1.x 的演进和扩展,而不是直接替换。如果把 HTTP 协议看做是面向对象编程,那么 SPDY/HTTP2.0 只扩展了一个子类,对于应用层来说,接口不变;如果把 HTTP 看做是操作系统,那么 SPDY/HTTP2.0 只是修改内核实现,而不改变系统调用接口。这样,我们可以像热插拔一样升级协议,而不用修改上层业务和框架,HTTP 对性能的提升对于上层来说都是透明的。

这里说的 HTTP 的接口指的是 HTTP 的通用语义,比如 HTTP 方法名、状态码、URI规范、头部字段等,这些对于业务层代码可见的特征。

SPDY 一开始只是作为一个实验性项目进行测试,同时也被很多三方应用到,在得到广泛认可后,将其核心的最有用的优化部分集成到了 HTTP2.0 中。后续 SPDY 和 HTTP2.0 并行研发,且 HTTP2.0 成为了后续的重点优化项目,而 SPDY 更多地充当了 HTTP2.0 的灰度版本,持续地在真实网络环境中验证各种优化手段,对验证成功的部分则集成到 HTTP2.0 中,所以,现在HTTP2.0 已经成为了最终稳定的 HTTP 未来版本了。



2 HTTP2.0的核心组成[Top]

HTTP2.0 在保持 HTTP 通用语义不变的前提下,针对 HTTP1.x 的问题进行了一系列的底层改进。根据上一节的描述我们不难推断,HTTP1.x 现存的最大问题就是——慢!具体体现在:

  • 一个 TCP 连接只承载一个 HTTP 请求/响应。这样并发请求需要建立多个连接,这样带来的问题有两个:一是连接的建立比较耗时,连接数越多越耗时;二是服务方在排队处理并发连接的时候,可能出现前面的连接处理太耗时,导致后面的连接进行空等,造成队头阻塞问题。针对这个问题,有人提出了连接复用方案,即创建连接缓存池,连接池里面的连接被回收前都不会关闭,可以重复用来处理更多的 HTTP 消息,但是一个 HTTP 消息不管多大都要占用一条连接,对网络带宽的利用是不够的,这直接影响了吞吐量并使得总体网络传输效率变慢。

  • 多个请求的处理是随机的,一个紧迫的请求并不会因为其更重要而更容易被服务器优先处理。

  • HTTP 头部字段是纯文本的,且相同的重复的头部文本在大量的消息里进行传输,对带宽是一种很大的浪费。

  • 没有服务器推送功能。

前三个缺点在互联网刚刚开始兴起的时候显得还不是特别突出,但是随着网络规模的日益扩大,流量暴涨,这些问题越来越成为了束缚网络体验的关键问题。所以,对以上几个问题的解决就成为了 HTTP2.0 的核心组成部分,即:

  • 通信格式的优化——帧、消息、数据流。

  • TCP连接多路复用。

  • 数据流优先级处理。

  • 流量控制。

  • 服务器推送。

  • 头部压缩技术。

下面分别介绍。



3 通信格式的优化——帧、消息、数据流[Top]

HTTP2.0 所有优化的基础在于帧的引入。帧是 HTTP2.0 消息格式中最基本的通信单元。在 HTTP1.x 中,HTTP 的消息头是基于文本格式的(消息体可以是文本格式也可以是二进制格式),整个消息不可分割,这样不便于进行压缩和多路复用等优化处理,而在 HTTP2.0 中,所有消息都被分割成更小的基于二进制的通信单元——帧,每个帧都有一个帧头部,描述了该帧所属哪个数据流。

在 HTTP1.x 中,一个 HTTP 消息分为消息头和消息体,在 HTTP2.0 中,被分割成两个帧:头部帧(对应消息头)和数据帧(对应消息体)。

Binary framing layer

另外,在消息头比较大的的情况下,一个消息头会被分成一个头部帧和多个头部延伸帧(CONTINUATION frames),且在发送时,它们都是连续排列的,即这些帧中间不能插入其它类型的或其它流的帧。

消息

HTTP2.0 中的消息即对应 HTTP1.x 中的一个请求或响应消息。一个消息被分割成多个帧,并与其它消息的帧交叉在一个 TCP 连接上传输。需要注意的是,同属一个消息的帧序列,虽然中间会穿插其它消息的帧,但是它们在逻辑上是按序发送的。

数据流

一个双向的字节流,由一个或多个消息组成。每个数据流都有一个唯一的标识符,每当发送一个头部帧的时候就会生成一个新的数据流。为了便于区分,由客户端发起的数据流都用奇数标识符,而由服务端发起的数据流(如推送消息)都用偶数标识符。

下图描述了帧、消息、数据流三者的关系:

Streams messages frames

图中数据流1包含一条请求消息和一条响应消息,而每个消息都包含一个或者多个帧。可以看到,同一个数据流是双向流动的,包含一个完整的请求响应过程。

另外,需要注意的是,帧只标识了其所属的流,而没有所谓的消息的标识,因为当发起一个新的请求时,必定以一个头部帧开始,而头部帧会触发一个新的数据流的建立,所以接收端在重组帧序列的时候,同一个流的帧按序重组后只会得到一个消息。即每个端只可能收到同一个流的一个消息,这样当然不需要在帧里再去区分是哪一个消息了。



4 TCP连接多路复用[Top]

根据上一节,我们知道接收端在收到帧的时候,只需要按照流标识符进行分组,然后按发送次序拼接就可以重组成一个完整的请求或者响应消息。这样在理论上就保证了,在一个 TCP 连接上可以同时穿插发送多个数据流,或者说可以同时交叉传输属于不同数据流的互不依赖的帧,达到在同一个连接上并行发送多条消息的目的,而这在 HTTP1.x 的时候需要建立多个 TCP 连接,且每个连接对应一个消息。下图描述了多路复用传输帧的效果:

Multiplexing

图中是一个传输快照,捕捉了同一个连接内并行的多个数据流。客户端正在向服务器传输一个 DATA 帧(数据流 5),与此同时,服务器正向客户端交错发送数据流 1 和数据流 3 的一系列帧。因此,一个连接上同时有三个并行数据流。

HTTP2.0 的多路复用节省了建立多个连接的代价,更好地利用了网络的传输带宽。由于数据可以分割成帧离散发送,所以当一个消息处理比较耗时,可以把已经处理完的数据包装成帧先发送出去,同时也不必阻塞后续消息的处理,后续消息的帧可以先于前面耗时消息处理完毕前先发出去,这样便解决了 HTTP1.x 中的队头阻塞问题。



5 数据流优先级处理[Top]

先看一段引用自《高性能浏览器网络》中的表述:

浏览器在渲染页面时,并非所有资源都具有相同的优先级:HTML 文档本身对构建 DOM 不可或缺,CSS 对构建 CSSOM 不可或缺,而 DOM 和 CSSOM 的构建都可能受到 JavaScript 资源的阻塞,其他资源(如图片)的优先级都可以降低。 为加快页面加载速度,所有现代浏览器都会基于资源的类型以及它在页面中的位 置排定请求的优先次序,甚至通过之前的访问来学习优先级模式——比如,之前的渲染如果被某些资源阻塞了,那么同样的资源在下一次访问时可能就会被赋予更高的优先级。 在 HTTP1.x 中,浏览器极少能利用上述优先级信息,因为协议本身并不支持多路复用,也没有办法向服务器通告请求的优先级。此时,浏览器只能依赖并行连接, 且最多只能同时向一个域名发送 6 个请求。于是,在等连接可用期间,请求只能 在客户端排队,从而增加了不必要的网络延迟。理论上,HTTP 管道可以解决这 个问题,只是由于缺乏支持而无法付诸实践。 HTTP2.0 一举解决了所有这些低效的问题:浏览器可以在发现资源时立即分派请 求,指定每个流的优先级,让服务器决定最优的响应次序。这样请求就不必排队了,既节省了时间,也最大限度地利用了每个连接。

简单说,在过去 HTTP1.x 时,客户端指定的消息优先级无法通知服务端,服务端不能根据消息的优先级合理利用计算资源,再加上并发度的限制,客户端对消息的优先级设置只能保证业务的准确,而无法保证业务的高效。而 HTTP2.0 因为在协议层面支持优先级通信,再加上多路复用带来的高并发优势,可以充分利用资源优先级提高传输效能。

HTTP2.0 对每一个数据流都可以设置权重和依赖,权重是一个 1-256 之间的整数,每个数据流可以指定另一个数据流的标识符作为父数据流从而构成依赖关系,这样最终可以呈现一个权重和依赖关系森林:

Stream prioritization

服务端在给数据流分配计算资源时,优先给被依赖的数据流分配,而在同一层的数据流,则根据权重不同,分配相应比例的资源。



6 每个源一个连接[Top]

HTTP2.0 的多路复用功能带来的一个优势是,可以显著减少 TCP 连接数,只要是与同一个源服务器之间的通信,都可以复用一条连接,且理论上可以做到一直不关闭。这对于大量短促的 HTTP 请求来说,可以节省大量的计算资源和网络延迟。

当然在 HTTP1.1 中也默认开启了长连接,但是因为没有多路复用,没有办法更充分利用长连接带来的便利。就好比长连接提供了一条宽敞的高速公路,但是在 HTTP1.1 中只能排队行驶一列汽车,而在 HTTP2.0 中可以充分利用高速公路的宽敞路面,并行行驶足够多的汽车。



7 流量控制[Top]

流量控制的目的是接收方为了限制发送方发送数据的速率,从而避免数据的处理需求超过接收方的处理能力或者避免不必要的数据发送。这在 TCP 中是自带的功能,但是 TCP 的流量控制是以 TCP 报文为单位来控制的,对应用层透明,无法处理应用级别的流量控制需求。比如在视频点播中,客户端暂停播放后,在 TCP 层面,服务器是无法理解的,可能仍然在持续发送视频流数据。

HTTP2.0 提供了基于数据流的滑动窗口机制来实现应用层协议级别的流量控制功能。在 HTTP2.0 中接收方会设置一个基于数据流的滑动窗口并向发送方公布,发送方会根据接收方窗口的大小来调整发送帧的大小。

另外,HTTP2.0 的流量控制是点到点的,而不是端到端的,比如 C 与 S 进行通信的路径为 C-A-B-S,那么会在 C-A 之间、A-B 之间、B-S 之间进行流量控制。



8 服务器推送[Top]

在 HTTP 的原有通信语义中是,一个请求对应一个响应。而在 HTTP2.0 中,打破了这个语义,使得请求与响应之间不再仅仅受限于一对一的关系,而是可以一个请求对应多个响应,这就是推送功能。推送的应用场景我们都很熟悉,如即时通信中新消息的送达、手机状态栏的通知系统等。HTTP2.0 在 HTTP 这一层开始提供语义级别的推送功能,这在以前可能需要通过借助 HTTP 长连接以及上层业务的改造来实现。

HTTP2.0 推送模型如下图所示:

Push

在客户端向服务器发起请求后(数据流 1),服务器除了对该请求要做出响应外,已经清楚知道后续一系列客户端需要的其它资源(数据流 2 和数据流 4)。这种情况下,不必等待客户端对后续资源发起新的请求了,而是直接将后续的资源也陆续发送给客户端即可,这样可以节省多余的请求流量,且减少网络延迟。

需要注意的是,服务器在对客户端的请求正式回应之前,会首先发送 promise 帧,这表示服务器将要对客户端发起推送了,所有的 promise 帧都要先于正式的资源响应帧发送,这样做的目的是提前告诉客户端将要有哪些资源发送过来,客户端在收到 promise 帧之后可以及时判断这些资源是否有发送的必要,比如本地已经缓存过了,那就没有必要推送过来了,这时可以向服务器发送 RST_STREAM 帧来拒绝相应的推送。



9 头部压缩技术[Top]

在 HTTP1.x 中,HTTP 头部以纯文本的方式进行传输,没有经过任何压缩,这对于少量的消息来说并不会有什么性能代价。但是,随着网络规模的扩大,大量这样未经压缩的消息头部充斥在网络中。一方面,大量的消息中充当有效负载的消息体的大小反而比消息头部要小;另一方面,大量的通信双方重复地发送了很多相同的消息头部字段。这些都造成了不必要的带宽浪费,以及不可忽视的网络延迟。

在 HTTP2.0 中,采用 HPACK 技术对 HTTP 头部进行了压缩,该技术的核心要点如下:

  • 支持对头部字段采用霍夫曼编码。霍夫曼编码的基本原理是对于常用字段采用更短的二进制编码,而长编码分配给不常用的字段,这遵循局部访问原理,使得传输中的大部分有效字段用更少的位数就可以表示出来,从而整体上减少了头部的大小。HTTP2.0 规范制定者在实践中对头部字段的访问频率进行了统计,并给出了推荐的 静态霍夫曼编码表

  • 通信双方维护一组相同的头部字段索引表,包括静态索引表和动态索引表,其特点是对于双方都知晓的头部字段记录在本地,这样在发送消息的时候,只需要发送各个字段的索引号即可,接收方根据收到的索引号从索引表中即可还原真正的字段值。静态索引表在 HTTP2.0 规范中定义,提供了一个包含所有连接都可能使用的常用 HTTP 标头字段(例如,有效标头名称)的列表;动态索引表最初为空,将根据在特定连接内交换的值进行更新。在通信过程中,将不断地根据特定连接的实际情况更新双方的静态索引表和动态索引表。索引表的引入进一步减少了头部字段的大小。下图描述了采用索引号传输后,有效载荷的减少情况:

Header compression

关于 HPACK 算法的细节,推荐一篇很好的 文章

huanzhiyazi avatar Apr 29 '21 03:04 huanzhiyazi