http2原理

Posted 推啊前端团队

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了http2原理相关的知识,希望对你有一定的参考价值。

http2简介-译文

  • http1.0与http1.1中存在的主要缺陷

  • SPDY

    • SPDY基础功能

    • SPDY高级功能

  • http2

    • 概述

    • 二进制分帧层

    • 数据流、消息和帧

    • 请求与响应复用

    • 数据流优先级

    • 每个来源一个连接

    • 流控制

    • 标头压缩

  • 如何使用Node搭建http2的web服务

http1.0与http1.1中存在的主要缺陷

在http1.0中存在的最主要的两个问题分别是链路无法复用以及HOLB(列头阻塞)。

链路无法复用直接导致了每一次请求都需要重新经历三次握手建立TCP连接与慢启动,三次握手无疑会增加大量延迟时间,这在高延迟的场景以及移动端叫为明显。http1.1中部分resolve了这个问题。

Head Of Line Blocking在第一个请求未得到响应前,后续请求只能排队等待,导致网络带宽无法被充分利用。http1.1部分resolve了这个问题。 能够将多个http请求借用一条TCP链路发出,也就是pipelining。然而没有根治,比如只有HEAD,GET,OPTIONS请求才可复用链路,POST请求依旧无法使用,因为可能存在请求间存在依赖关系;亦或是服务端遵循FIFO原则依次响应,或其它缺陷。

无服务端推送

SPDY

http1.x依然存在两个痛点,即延迟与安全性,直到2012年google提出了SPDY方案,通过多路复用、请求优先级、header压缩等功能来解决http1.x的遗留问题,但是google决定在2016年不再继续支持SPDY的开发。就在这个SPDY催化剂的作用下,http2应运而生,其实http2就是以SPDY为原型进行讨论和制定的

为降低延迟,C端的单连接单请求,server端的FIFO响应队列都是延迟的大头

http最初设计就是C端发请求,server端响应,server无法主动push内容到C端

压缩http header,由于业务需求膨胀导致cookie与UA体积增大,很容易达到1kb大小,由于http是无状态特性,header必须每次request都需要重复携带,较为耗费流量。

SPDY设计的层级关系为

SPDY基础功能

多路复用(multiplexing)。多路复用通过多个请求stream并标识唯一id共享一个tcp连接的方式,解决了http1.x holb的问题,降低了延迟同时提高了带宽利用率

请求优先级(request prioritization)。多路复用会带来一个新的问题是,在连接共享的基础之上有可能会导致关键请求被阻塞。SPDY允许给每个request设置优先级,这样重要的请求就会有限得到响应。比如浏览器加载首页,首页的html内容应该优先展示,之后才是各种静态资源文件,脚本文件等加载,这样可以保证用户能第一时间看到网页内容

header压缩。SPDY对header的压缩率可以达到80%以上,低带宽环境下效果较大。通常丢包率越大,影响越大

SPDY高级功能

server推送(server push)。开启server push之后,server通过X-Associated-Content header告知C端会有新的内容推送过来。server暗示(server hint)。sever hint不会主动push内容,只是告诉有心的内容产生,内容的下载还是需要C端主动发起请求。server hint通过X-Subresources header来通知,可用于服务端导出的场景。

HTTP2

概述

HTTP/2 可以让我们的应用更快、更简单、更稳定 - 这几词凑到一块是很罕见的!HTTP/2 将很多以前我们在应用中针对 HTTP/1.1 想出来的“歪招儿”一笔勾销,把解决那些问题的方案内置在了传输层中。不仅如此,它还为我们进一步优化应用和提升性能提供了全新的机会!

HTTP/2 的目的是通过支持完整的请求与响应复用来减少延迟,通过有效压缩 HTTP 标头字段将协议开销降至最低,同时增加对请求优先级和服务器推送的支持。为达成这些目标,HTTP/2 还给我们带来了大量其他协议层面的辅助实现,例如新的流控制、错误处理和升级机制。上述几种机制虽然不是全部,但却是最重要的,每一位网络开发者都应该理解并在自己的应用中加以利用。

HTTP/2 没有改动 HTTP 的应用语义。HTTP 方法、状态代码、URI 和标头字段等核心概念一如往常。不过,HTTP/2 修改了数据格式化(分帧)以及在客户端与服务器间传输的方式。这两点统帅全局,通过新的分帧层向我们的应用隐藏了所有复杂性。因此,所有现有的应用都可以不必修改而在新协议下运行。

二进制分帧层

http2所有性能增强的核心在于新的二进制分帧层,它定义了如何封装http消息并在客户端与服务器之间传输。

这里所谓的层不是在网络协议协议栈中又新增了一层,而是指一种新的编码机制:http的语义都不受影响,不同的是传输期间对他们的编码方式改变了。http1.x采用换行符分割的文本编码,而http2将所传输信息分割成更小的消息和帧,并用二进制编码。 这一切的改变对应用层透明。

数据流、消息和帧

新的二进制分帧层机制改变了客户端与服务器之间交换数据的方式。其中引入了三个概念用于理解:

  • 数据流(data stream): 已建立的连接内的双向字节流,用于承载一条或多条消息

  • 消息(message):与逻辑请求和响应相对应的完整的一系列帧

  • 帧(frame): http2通信的最小单位,每个帧都包含帧头,因为至少会标识出所属数据流

这些概念的关系如下:

  • 所有通信都在一个TCP连接上完成,此连接可以承载任意数量的双向数据流(可以断定网页仅需一个TCP连接)

  • 每个数据流都有一个唯一标识符和可选的优先级信息,用于承载双向消息(前端也许可以通过webAPI指定某次HTTP请求的优先级)

  • 每条消息都是一条逻辑HTTP消息,包含一个或多个帧(推断可能是利用TCP的分片传输机制)

  • 帧是最小的通信单位,承载着特定类型的数据,例如HTTP标头、消息负载等等。来自不同数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装。(类似TCP分片传输机制)

简而言之,http2将http协议通信分解为二进制编码帧的交换,这些帧对应着特定数据流中的消息。所有这些都在一个TCP连接内复用。这是http2协议所有其他功能和性能优化的基础。

请求与响应复用

在HTTP/1.x中,如果客户端想要发起多个并行请求以提升性能,则必须使用多个TCP连接。这是http/1.x的交付模型,该模型可以保证每个连接每次只交付一个响应(响应排队)。更糟糕的是,这种模型也会导致队首阻塞(HoLB),从而导致底层TCP连接利用率底下。

http2中的新的二进制分帧层突破了这些限制,实现了完整的请求和响应复用:client和server可以将http消息分解为互不依赖的帧,然后交错发送,最后再另一端重组。

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

将http消息分解为独立的帧,交错发送,然后在另一端重组是http2的最重要的一项增强。Actually,这个机制会在整个网络技术栈中引发一系列连锁反应,从而带来巨大的性能提升,让我们可以:

  • 并行交错的发送多个请求,请求之间互不影响

  • 并行交错的发送多个响应,响应之间互不干扰

  • 公用一个连接发送多个请求以及响应

  • 不必再为绕过http1.x限制而做很多工作(例如级联文件、image sprites、域名分片)

  • 消除不必要的延迟和提高现有网络容量的利用率,从而减少页面加载时间

http2中的新二进制分帧层解决了http1.x中存在的队首阻塞问题,也消除了并行处理和发送请求及响应时对多个连接的依赖。结果,应用速度更快、开发更简单、部署成本更低。

数据流优先级

将http消息分解为多个独立的帧之后,我们就可以复用多个数据流中的帧,客户端和服务器交错发送和传输这些帧的顺序就成为关键的性能决定因素。为了做到这一点,http2标准允许每个数据流都有一个关联的权重和依赖关系:

  • 可以向每个数据流分配一个介于1至256之间的整数

  • 每个数据流与其他数据流之间可以存在显式依赖关系

数据流依赖关系和权重的组合让客户端可以构建和传递“优先级树”,表明它倾向于如何接收响应。反过来,服务器可以使用此信息通过控制CPU、内存和其他资源的分配设定数据流处理的priority,在资源数据可用之后,带宽分配可以确保将高priority响应以最优方式传输至客户端。

http2内部的数据流依赖关系通过将另一个数据流的唯一标识符作为父项引用进行声明;如果忽略标识符,相应数据流将依赖于“根数据流”。声明数据流依赖关系指出,应尽可能先向父数据流分配资源,然后再向其依赖项分配资源。(此处示例图片非常清晰,不赘述了)

如上图所示,数据流依赖关系与权重的组合明确表达了资源优先级,这是一种用于提升浏览器性能的关键功能,网络中拥有多种资源类型,它们的依赖关系和权重各不相同。不仅如此,http2协议还允许客户端随时更新这些priority,进一步优化浏览器性能。换句话说,我们可以根据用户互动和其他信号更改依赖关系和重新分配权重。

注:数据流依赖关系和权重的组合表示传输优先级,而不是要求,因此不能保证特定的处理或传输顺序。即,客户端无法强制服务器通过数据流优先级以特定顺序处理数据流。尽管这看起来违反直觉,但却是一种不必要行为。我们不希望在优先级较高的资源受阻时,还阻止服务器处理优先级较低的资源。

每个来源一个链接

有了新的分帧机制后,http2不再依赖多个TCP连接去并行复用数据流;每个数据都拆分成很多帧,而这些帧可以交错,还可以分别设定优先级。因此,所有http2连接都是永久的,而且仅需要每个来源一个连接,随之带来诸多性能优势。

大多数http传输是短暂而急促的,而TCP则针对长时间的批量数据传输进行了优化。通过重用相同的连接,http2既可以有效的利用每个TCP连接,也可以整体降低协议开销。不仅如此,使用更少的连接还可以减少占用的内存和处理空间,也可以缩短完整连接路径(即客户端、可信中介和源服务器的路径),这降低了整体运行成本并提高了网络利用率和容量。因此,迁移到http2不仅可以减少网络延迟,还有助于提高通量和降低运行成本。

注:连接数量减少对提升https部署的性能来说是一项特别重要的功能:可以减少开销较大的TLS连接数、提升会话重用率,以及整体上减少所需的客户端和服务器资源

流控制

流控制是一种阻止发送方向接收方发送大量数据报文的机制,以免超出后者的需求或者处理能力:发送方可能非常繁忙、处于较高负载之下,也可能仅仅希望为特定数据流分配固定量的资源。例如,客户端可能请求了一个具有较高优先级的大型视频流,但是用户已经暂定视频,客户端现在希望暂停或限制从服务器传输,以免提取与缓冲不必要的数据。再比如,一个代理服务器可能具有较快的下游连接和较慢的上游连接,并且也希望调节下游连接传输数据的速度以及匹配上游连接的速度来控制其资源利用率

这跟TCP流控制原理基本相同。不过,由于http2数据流在一个TCP连接内复用,TCP流控制既不够精细,也无法提供必要的应用级API来调节各个数据流的传输。为了解决这一问题,http2提供了一组简单的构建块,这些构建块允许客户端和服务器实现其自己的数据流控制和连接级流控制:

  • 流控制具有方向性。每个接收方都可以根据自身需要选择为每个数据流和整个连接设置任意的窗口大小。

  • 流控制基于信用。每个接收方都可以公布其初始连接和数据流流控制窗口(以字节为单位),每当发送方发出DATA帧时都会减小,在接收方发出WINDOW_UPDATE帧时增大

  • 流控制无法停用。建立http2连接后,客户端将与服务器交换SETTINGS帧,这会在两个方向上设置流控制窗口。流控制窗口的默认值为65535字节,但是接收方可以设置一个较大的最大窗口大小(最大为2^31 - 1字节),并在接收到任意数据时通过发送WINDOW_UPDATE帧来维持这一大小

  • 流控制为逐跃点控制,而非端到端控制。即,可信中介可以使用它来控制资源使用,以及基于自身条件和启发式算法实现资源分配机制。

http2未指定任何特定算法来实现流控制。不过,它提供了简单的构建块并推迟了客户端和服务器实现,可以实现自定义策略来调节资源使用和分配,以及实现新传输能力,同时提升网络应用的实际性能和感知性能

例如,它允许浏览器提取图像预览或首次扫描结果,进行显示并允许其他高优先级提取继续,然后在更关键的资源完成加载后恢复提取

服务器推送

http2在应用层输出的最令人振奋的功能是,服务器可以对一个客户端请求发送多个响应。换句话说,除了对最初请求的响应外,服务器还可以向客户端推送额外资源,而无需客户端明确的请求。

注:http2打破了严格的请求-响应语义,支持一对多和服务器发起的推送工作流,在浏览器内外开启了全新的互动可能性。这是一项使能功能,对我们思考协议、协议用途和使用方式具有重要的长期影响。

为什么在浏览器中需要此类机制呢?一个典型的网络应用包含多种资源,客户端需要检查服务器提供的文档才能逐个找到他们。那为什么不让服务器提前推送这些资源,从而减少额外的延迟时间呢?服务器已经知道客户端下一步要请求什么资源,这时候服务器推送可派上用场。

事实上,如果您在网页中内联过CSS、javascript,或者通过data URLs内联过其他资源,那么您就已经亲身体验过server push了。对于将资源手动内联到文档中的过程,我们实际上是在将资源推送给客户端,而不是等待客户端请求。使用http2,我们不仅可以实现相同结果,还会获得其他性能优势。推送资源可以进行如下处理:

  • 由客户端缓存

  • 在不同页面之间重用

  • 与其他资源一起复用

  • 由服务器设定优先级

  • 被客户端拒绝

PUSH_PROMISE 101

所有服务器推送数据流都由PUSHPROMISE帧发起,表明了服务器向客户端推送所述资源的意图,并且需要先于请求推送资源的响应数据传输。这种传输顺序非常重要:客户端需要了解服务器打算推送哪些资源,以免这些资源创建重复请求。满足此要求的最简单策略是先于父响应(即,DATA帧)发送所有PUSHPROMISE帧,其中包含所承诺资源的http标头。

在客户端接收到PUSHPROMISE帧后,它可以根据自身情况选择拒绝数据流(通过RESSTREAM帧)。(如果资源已经位于缓存中,可能会发生这种情况。)这是一个相对于http/1.x是必要的提升。相比之下,使用资源内联等同于“强制推送”:客户端无法选择拒绝、取消或单独处理内联的资源。

使用http2,客户端仍然完全掌控服务器推送的使用方式。客户端可以限制并行推送的数据流数量;调整初始流控制窗口以控制在数据流首次打开时推送的数据量;或完全停用服务器推送。这些优先级在http2连接开始时通过SETTINGS帧传输,可能随时更新。

推送的每个资源都是一个数据流,与内嵌资源部不同,客户端可以对推送的资源逐一复用、设定优先级和处理。浏览器强制执行的唯一安全限制是,推送的资源必须符合原点相同这一策略:服务器对所推内容必须具有权威性。

标头压缩

每个http传输都承载了一组标头,这些标头说明了传输的资源及其元属性。在http1.x中,此元数据始终以纯文本形式,通常会给每个传输增加500-800字节的开销。如果使用http Cookie,增加的开销有时候会达到上千字节。为了减少此功能开销和提升性能,http2使用HPACK压缩格式压缩请求和响应标头元数据,这种格式采用两种简单但是强大的技术:

  • 这种格式支持通过静态Huffman代码对传输的标头进行编码,从而减少了各个传输的大小。

  • 这种格式要求客户端和服务端同时维护和更新一个包含之前见过的标头字段的索引列表(换句话说,他可以建立一个共享的压缩上下文),此列表随后会用作参考,对之前传输的值进行有效编码

利用Huffman编码,可以在传输时对各个值进行压缩,而利用之前传输值的索引列表,我们可以通过对传输索引值的方式对重复值进行编码,索引值可用于有效查询和重构完整的标头键值对。

作为一种进一步优化方式,HPACK压缩上下文包含一个静态列表和动态列表:静态表在规范中定义,并提供了一个包含所有连接都可能使用的常用http标头字段的列表;动态表最初为空,将根据在特定连接内交换的值进行更新。因此,为之前未见过的值采用静态Huffman编码,并替换每一侧静态表或动态表中已存在的索引,可以减少每个请求的大小。

注:在http中,请求和响应标头字段的定义保持不变,仅有一些微小的差异:所有标头端名称均为小写,请求行现在拆分成各个:method、:scheme、:authority和:path伪标头字段。

HPACK的安全性和性能

早起版本的http和SPDY使用zlib(带有一个自定义字典)压缩有所http标头。这种方式可以将所传输标头数据的大小减小85% - 88%,显著减少了页面加载时间延迟。

然而2012年夏天,出现了针对TLS和SPDY压缩算法的“犯罪”安全攻击,此攻击会导致会话被劫持。于是,zlib压缩算法被HPACK替代,后者经过专门设计,可以解决发现的安全问题、实现起来也更搞笑和简单,当然,可以对http标头元数据进行良好压缩。

如何使用Node搭建http2的web服务

这里使用node v9+版本built-in的http2模块

 
   
   
 
  1. openssl req -x509 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' \

  2.  -keyout localhost-privkey.pem -out localhost-cert.pem

 
   
   
 
  1. const http2 = require('http2')

  2. const fs = require('fs')


  3. const server = http2.createSecureServer({

  4.  key: fs.readFileSync(__dirname + '/../localhost-privkey.pem'),

  5.  cert: fs.readFileSync(__dirname + '/../localhost-cert.pem')

  6. })


  7. server.on('error', err => console.errr(err))

  8. server.on('socketError', err => console.error(err))


  9. server.on('stream', (stream, headers) => {

  10.  // 流是双工的

  11.  stream.respond({

  12.    'content-type': 'application/json',

  13.    ':status': 200

  14.  })

  15.  for (const name in headers) {

  16.    console.log(`${name} : ${headers[name]}`)

  17.  }

  18.  stream.end('{"name": "Cecil"}')

  19. })


  20. server.listen(8443)

然后使用node编写http2client或者在Chrome中输入https://localhost:8443访问服务

了解http2模块,请至node http2模块使用http2开发业务,请至Fastify。

以上是关于http2原理的主要内容,如果未能解决你的问题,请参考以下文章

HTTP2.0 原理详解

http2协议之底层原理抓包分析与视频讲解

头条http2 中的首部压缩的实现原理是什么

gRPC之流式调用原理http2协议分析

4月第4题:http2首部压缩是什么原理?

TrafficServer http2 代码分析