Advanced-Frontend/Daily-Interview-Question

第 15 题:简单讲解一下 http2 的多路复用

ZodiacSyndicate opened this issue · 30 comments

HTTP2采用二进制格式传输,取代了HTTP1.x的文本格式,二进制格式解析更高效。
多路复用代替了HTTP1.x的序列和阻塞机制,所有的相同域名请求都通过同一个TCP连接并发完成。在HTTP1.x中,并发多个请求需要多个TCP连接,浏览器为了控制资源会有6-8个TCP连接都限制。
HTTP2中

  • 同域名下所有通信都在单个连接上完成,消除了因多个 TCP 连接而带来的延时和内存消耗。
  • 单个连接上可以并行交错的请求和响应,之间互不干扰

在 HTTP/1 中,每次请求都会建立一次HTTP连接,也就是我们常说的3次握手4次挥手,这个过程在一次请求过程中占用了相当长的时间,即使开启了 Keep-Alive ,解决了多次连接的问题,但是依然有两个效率上的问题:

  • 第一个:串行的文件传输。当请求a文件时,b文件只能等待,等待a连接到服务器、服务器处理文件、服务器返回文件,这三个步骤。我们假设这三步用时都是1秒,那么a文件用时为3秒,b文件传输完成用时为6秒,依此类推。(注:此项计算有一个前提条件,就是浏览器和服务器是单通道传输)
  • 第二个:连接数过多。我们假设Apache设置了最大并发数为300,因为浏览器限制,浏览器发起的最大请求数为6,也就是服务器能承载的最高并发为50,当第51个人访问时,就需要等待前面某个请求处理完成。

HTTP/2的多路复用就是为了解决上述的两个性能问题。
在 HTTP/2 中,有两个非常重要的概念,分别是帧(frame)和流(stream)。
帧代表着最小的数据单位,每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流。
多路复用,就是在一个 TCP 连接中可以存在多条流。换句话说,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。通过这个技术,可以避免 HTTP 旧版本中的队头阻塞问题,极大的提高传输性能。

简单来说, 就是在同一个TCP连接,同一时刻可以传输多个HTTP请求。

之前是同一个连接只能用一次, 如果开启了keep-alive,虽然可以用多次,但是同一时刻只能有一个HTTP请求

http/1中的每个请求都会建立一个单独的连接,除了在每次建立连接过程中的三次握手之外,还存在TCP的慢启动导致的传输速度低。其实大部分的http请求传送的数据都很小,就导致每一次请求基本上都没有达到正常的传输速度。

在http1.1中默认开启keep-alive,解决了上面说到的问题,但是http的传输形式是一问一答的形式,一个请求对应一个响应(http2中已经不成立,一个请求可以有多个响应,server push),在keep-alive中,必须等下上一个请求接受才能发起下一个请求,所以会收到前面请求的阻塞。

使用pipe-line可以连续发送一组没有相互依赖的请求而不比等到上一个请求先结束,看似pipe-line是个好东西,但是到目前为止我还没见过这种类型的连接,也间接说明这东西比较鸡肋。pipe-line依然没有解决阻塞的问题,因为请求响应的顺序必须和请求发送的顺序一致,如果中间有某个响应花了很长的时间,后面的响应就算已经完成了也要排队等阻塞的请求返回,这就是线头阻塞。

http2的多路复用就很好的解决了上面所提出的问题。http2的传输是基于二进制帧的。每一个TCP连接中承载了多个双向流通的流,每一个流都有一个独一无二的标识和优先级,而流就是由二进制帧组成的。二进制帧的头部信息会标识自己属于哪一个流,所以这些帧是可以交错传输,然后在接收端通过帧头的信息组装成完整的数据。这样就解决了线头阻塞的问题,同时也提高了网络速度的利用率。

HTTP1.X的线头阻塞问题指的是统一域名下的请求会有阻塞问题吗?还是说页面的所有请求都必须按顺序相应

HTTP1.X的线头阻塞问题指的是统一域名下的请求会有阻塞问题吗?还是说页面的所有请求都必须按顺序相应

正常情况下一个http请求就会建立一个TCP连接,在这种情况下就不存在线头阻塞。但是当多个请求复用一个TCP连接时才可能出现这个问题。不同域名的请求当然不能共用一个TCP连接,而且就算是同一个域名下的请求,也要看服务器支不支持pipeline。即使服务器支持pipeline,也要根据自己的实际使用场景来决定要不要以pipeline的形式发送请求。

http/1.0:如需要发送多个请求必须创建多个 TCP 连接,并且浏览器对于单域名请求有数量限制(一般6个),其连接无法被复用

http/1.1:引入流水线(Pipelining)技术,但先天 FIFO(先进先出)机制导致当前请求的执行依赖于上一个请求执行的完成,容易引起报头阻塞,并没有从根本上解决问题

http/2:重新定义底层 http 语义映射,允许同一个连接上使用请求和响应双向数据流。同一域名只需占用一个 TCP 连接,通过数据流(Stream)以帧为基本协议单位,从根本上解决了问题,避免了因频繁创建连接产生的延迟,减少了内存消耗,提升了使用性能

资料:《koa 与 nodejs 开发实战》 http 篇

简单版回答:

HTTP/2 复用 TCP 连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,而且不用按照顺序一一对应。

举例来说,在一个TCP连接里面,服务器同时收到了A请求和B请求,于是先回应A请求,结果发现处理过程非常耗时,于是就发送A请求已经处理好的部分, 接着回应B请求,完成后,再发送A请求剩下的部分。

历史原因解释:

1、HTTP/1.0 版本

该版本主要缺点是,每个TCP连接只能发送一个请求。发送数据完毕,连接就关闭,如果还要请求其他资源,就必须再新建一个连接。为了解决这个问题,需要使用 Connection: keep-alive 这个字段。

2、HTTP/1.1 版本

该版本引入了持久连接(persistent connection),即 TCP 连接默认不关闭,可以被多个请求复用,不用声明 Connection: keep-alive。还引入了管道机制(pipelining),即在同一个TCP连接里面,客户端可以同时发送多个请求。这样就进一步改进了HTTP协议的效率。

虽然1.1版允许复用TCP连接,但是同一个TCP连接里面,所有的数据通信是按次序进行的。服务器只有处理完一个回应,才会进行下一个回应。要是前面的回应特别慢,后面就会有许多请求排队等着。这称为"队头堵塞"(Head-of-line blocking)。

在http1.1 中 默认允许 connect: keep-alive 但是在一个TCP里面 数据通信时按次进行,也就是说第二次请求发送要在第一次响应后进行,若第一次响应慢,则要一直阻塞。这个问题就是对头阻塞。
在HTTP/2.0中,在一个TCP链接中,客户端和服务器可以同时发送多个请求和响应,则避免了对头阻塞, 实现了 双向 实时 多工。 在2.0中 采用了数据流,对同一个请求或响应的所有数据包做了一个独一无二的标识,所以可以不用等待发送。两端会根据标识组装数据流。

上面说的都挺多了,我补充个header压缩吧,23333
这个在http 1.1中,jwt太大了真的是还不能压缩占据了很多带宽,2.0后可以压缩头部,还是很不错滴~

在 HTTP/1 中,每次请求都会建立一次HTTP连接,也就是我们常说的3次握手4次挥手,这个过程在一次请求过程中占用了相当长的时间,即使开启了 Keep-Alive ,解决了多次连接的问题,但是依然有两个效率上的问题:

  • 第一个:串行的文件传输。当请求a文件时,b文件只能等待,等待a连接到服务器、服务器处理文件、服务器返回文件,这三个步骤。我们假设这三步用时都是1秒,那么a文件用时为3秒,b文件传输完成用时为6秒,依此类推。(注:此项计算有一个前提条件,就是浏览器和服务器是单通道传输)
  • 第二个:连接数过多。我们假设Apache设置了最大并发数为300,因为浏览器限制,浏览器发起的最大请求数为6,也就是服务器能承载的最高并发为50,当第51个人访问时,就需要等待前面某个请求处理完成。

HTTP/2的多路复用就是为了解决上述的两个性能问题。
在 HTTP/2 中,有两个非常重要的概念,分别是帧(frame)和流(stream)。
帧代表着最小的数据单位,每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流。
多路复用,就是在一个 TCP 连接中可以存在多条流。换句话说,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。通过这个技术,可以避免 HTTP 旧版本中的队头阻塞问题,极大的提高传输性能。

第二个问题里说到连接数过多?开了keep-alive怎么还连接数过多?复用了相同域名的连接,怎么还会过多呢?

多路复用

  • 同一域名下,只需要建立一个连接。 => 减少握手等待时间,以及多个 tcp 竞争带宽。
  • 单个连接可以承受任意数量的双向数据流。 => 并行多个请求响应。
  • 数据流以消息的形式发送,消息由一个或多个帧组成;帧可以乱序发送,根据帧头部的流标识重新组装。 => 可以设置一个 31 bit 的优先级,有了这个优先值,客户端和服务器就可以在处理不同的流时采取不同的策略,以最优的方式发送流、消息和帧。

所以 http/2 的多路复用属于 时分复用,频分复用,空分复用还是码分复用呢。。。。

http 2.0

HTTP2 采用二进制格式传输

相对于 HTTP1.x 的文本格式,二进制格式解析更高效。关键之一就是在应用层(HTTP/2)和传输层(TCP or UDP)之间增加一个二进制分帧层。
binary-framing

在二进制分帧层中, HTTP2 会将所有传输的信息分割为更小的消息和帧(frame),并对它们采用二进制格式的编码。

多路复用

代替了 HTTP1.x 的序列和阻塞机制,所有的相同域名请求都通过同一个 TCP 连接并发完成。在 HTTP1.x 中,并发多个请求需要多个 TCP 连接,浏览器为了控制资源会有 6-8 个 TCP 连接都限制。

HTTP2 中

  • 同域名下所有通信都在单个连接上完成,消除了因多个 TCP 连接而带来的延时和内存消耗。
  • 单个连接上可以并行交错的请求和响应,之间互不干扰

Header Compression

HTTP2 使用了专门为首部压缩而设计的 HPACK 算法。

服务端推送(Server Push)

Reference

其实在某些情景下,多路复用(相比于多个连接)可能不会带来性能上的提升。
但是解决了绝大部分场景的问题

其实在某些情景下,多路复用(相比于多个连接)可能不会带来性能上的提升。
但是解决了绝大部分场景的问题

for example?

在 HTTP/1 中,每次请求都会建立一次HTTP连接,也就是我们常说的3次握手4次挥手,这个过程在一次请求过程中占用了相当长的时间,即使开启了 Keep-Alive ,解决了多次连接的问题,但是依然有两个效率上的问题:

  • 第一个:串行的文件传输。当请求a文件时,b文件只能等待,等待a连接到服务器、服务器处理文件、服务器返回文件,这三个步骤。我们假设这三步用时都是1秒,那么a文件用时为3秒,b文件传输完成用时为6秒,依此类推。(注:此项计算有一个前提条件,就是浏览器和服务器是单通道传输)
  • 第二个:连接数过多。我们假设Apache设置了最大并发数为300,因为浏览器限制,浏览器发起的最大请求数为6,也就是服务器能承载的最高并发为50,当第51个人访问时,就需要等待前面某个请求处理完成。

HTTP/2的多路复用就是为了解决上述的两个性能问题。
在 HTTP/2 中,有两个非常重要的概念,分别是帧(frame)和流(stream)。
帧代表着最小的数据单位,每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流。
多路复用,就是在一个 TCP 连接中可以存在多条流。换句话说,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。通过这个技术,可以避免 HTTP 旧版本中的队头阻塞问题,极大的提高传输性能。

第二个问题里说到连接数过多?开了keep-alive怎么还连接数过多?复用了相同域名的连接,怎么还会过多呢?

虽然http1.1默认开启了keep-alive, 使得tcp可以复用了, 但由于"队头阻塞(Head-of-line blocking)"的存在, 后面请求可能会由于前面的请求时长过长而阻塞; 因此浏览器在同时发出n条请求时, 会建立n条tcp连接(当然并不是无限制开启, 不然就和http1.0无区别了, 比如chrome是限制同一时刻最多六条), 当请求数超过n条时, 才会复用tcp连接;

假设我们统一使用chrome, 那每个人最大的建立的连接数为6;
当服务器设置了最大并发数为300, 最多可满足50个客户端(客户端建立最大连接数)同时访问服务器, 当第51个客户端访问时, 则需要排队~

HTTP2采用二进制格式传输,取代了HTTP1.x的文本格式,二进制格式解析更高效。
多路复用代替了HTTP1.x的序列和阻塞机制,所有的相同域名请求都通过同一个TCP连接并发完成。在HTTP1.x中,并发多个请求需要多个TCP连接,浏览器为了控制资源会有6-8个TCP连接都限制。
HTTP2中

  • 同域名下所有通信都在单个连接上完成,消除了因多个 TCP 连接而带来的延时和内存消耗。
  • 单个连接上可以并行交错的请求和响应,之间互不干扰

如果同域名不同端口呢?也是一个连接吗

请教个问题,一个 HTTP2 消息被分成头部帧和数据帧之后,重新装配时,怎么知道这两个帧是属于同一个消息呢?流标识只能表示它们在同一个流里,但是一个流可以包含很多条消息,所以流标识是不能判断两个帧是否是同一个消息的,那么如何判断呢?

Soyn commented

请教个问题,一个 HTTP2 消息被分成头部帧和数据帧之后,重新装配时,怎么知道这两个帧是属于同一个消息呢?流标识只能表示它们在同一个流里,但是一个流可以包含很多条消息,所以流标识是不能判断两个帧是否是同一个消息的,那么如何判断呢?

https://datatracker.ietf.org/doc/html/rfc7540#section-4.3. RFC的这一节有讲你说的问题。

在http2.0中,连接支持多路复用,但是后端如果在每个请求的响应头部中返回Connection为close,请问下该连接会关闭吗?

请教个问题,一个 HTTP2 消息被分成头部帧和数据帧之后,重新装配时,怎么知道这两个帧是属于同一个消息呢?流标识只能表示它们在同一个流里,但是一个流可以包含很多条消息,所以流标识是不能判断两个帧是否是同一个消息的,那么如何判断呢?

https://datatracker.ietf.org/doc/html/rfc7540#section-4.3. RFC的这一节有讲你说的问题。

这个只是讲了头部帧分片后,如何重新装配的问题。我问的是重新装配时,怎么确定一个消息的头部帧和数据帧属于同一个消息

请教个问题,一个 HTTP2 消息被分成头部帧和数据帧之后,重新装配时,怎么知道这两个帧是属于同一个消息呢?流标识只能表示它们在同一个流里,但是一个流可以包含很多条消息,所以流标识是不能判断两个帧是否是同一个消息的,那么如何判断呢?

请问这个问题解决了吗? 查了很多资料都是泛泛而谈,没有具体细节

这个理论是真理论啊,有没有可观察可实现的?

有个疑惑,http2单个连接多路复用处理多个请求,请求个数也是有上限的吧?否则大量请求还会有堆积吧?

这个理论是真理论啊,有没有可观察可实现的?

image

面试八股:个人见解,欢迎大家指正qaq

首先,我认为 HTTP2 中的多路复用,属于是 时分复用,对同一个域名只建立一条连接,充分压榨 tcp 全双工的特性,存在两个流方向,流中并发传输数据帧,最后根据 流id, 帧id 以及控制位复原流,解决 http1.x 的对头堵塞问题,将单一请求的时延,转化成为多个请求的时延,降低用户对单一时延的感知。
注意:http2 rfc 提到 2 出现的历史背景有 目前浏览器的请求响应数量日益增多。

有个疑惑,http2单个连接多路复用处理多个请求,请求个数也是有上限的吧?否则大量请求还会有堆积吧?

如果是说服务端,那基本不太可能会有单个页面请求直接打到服务端上限的可能,集群扩展下抗成百上千qps都很简单;
如果是说前端,虽然协议是无限的多路复用,但显然浏览器、native请求池都是有上限的,比如很早chrome只允许40个左右的并发请求,app端的请求池数量就更少了,可分配的量仍然是有限的

其实在某些情景下,多路复用(相比于多个连接)可能不会带来性能上的提升。 但是解决了绝大部分场景的问题

确实是这样,因为用单个tcp通道还是没有解决tcp层面的阻塞问题,导致丢包率上升;因此才有http3的出现

其实在某些情景下,多路复用(相比于多个连接)可能不会带来性能上的提升。
但是解决了绝大部分场景的问题

for example?

比如弱网情况下,因为一个域名只有一条连接,TCP头部阻塞会影响该帧流的所有请求