文章目录
- HTTP版本演变
-
- HTTP/0.9
- HTTP/1.0
- HTTP/1.1
- HTTP/2
- HTTP/3(解决TCP队头阻塞)
-
- [HTTP/3 基于 QUIC 协议,具有以下特点:](#HTTP/3 基于 QUIC 协议,具有以下特点:)
- HTTP/2的缺点
- QUIC协议的特点(将UDP变成可靠的)
- 无队头阻塞
- 更快的连接建立
- 连接迁移
HTTP版本演变
目前为止,HTTP 常见的版本有 HTTP/1.1,HTTP/2.0,HTTP/3.0,不同版本的 HTTP 特性是不一样的。
HTTP/0.9
HTTP/0.9 是最早的HTTP版本,在1991年就已经发布,只支持GET方法,也没有请求头,服务器只能返回 HTML格式的内容。
HTTP/1.0
HTTP/1.0 是HTTTP 协议的第一个正式版本,主要具有以下特性:
- 引入了请求头和响应头,支持多种请求方法和状态码
- 不支持持久连接,每次请求都需要建立新的连接
HTTP/1.1
新引入:
- 引入长连接:只要客户端和服务器任意⼀端没有明确提出断开连接,则保持TCP连接状态。
- 管道网络传输,管道化技术:同一个 TCP 连接里面,客户端可以发起多个请求,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去。
- 管道解决了请求的队头阻塞问题,但是没有解决响应的队头阻塞。
问题:
- 头部冗余:每个请求和响应都需要带有一定的头部信息,每次互相发送相同的首部造成的浪费较多;
- 服务器是按请求的顺序响应的,如果服务器响应慢,会招致客户端一直请求不到数据,也就是响应队头阻塞;
- 没有请求优先级控制;
- 请求只能从客户端开始,服务器只能被动响应。
长连接是什么:
持久连接,只要客户端和服务器任意⼀端没有明确提出断开连接,则保持TCP连接状态。
管道网络传输:
在同一个 TCP 连接里面,客户端可以发起多个请求,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。
客户端需要请求两个资源。以前的做法是,在同一个 TCP 连接里面,先发送 A请求,然后等待服务器做出回应收到后再发出 B 请求。那么,管道机制则是允许浏览器同时发出 A请求和 B 请求。
但是服务器必须按照接收请求的顺序发送对这些管道化请求的响应,如果服务端在处理 A 请求时耗时比较长,那么后续的请求的处理都会被阻塞住,这称为「队头堵塞」。所以,HTTP/1.1 管道解决了请求的队头阻塞,但是没有解决响应的队头阻塞。
HTTP/1.1 管道化技术不是默认开启,而且浏览器基本都没有支持。
队头阻塞是什么?
当顺序发送的请求序列中的一个请求因为某种原因被阻塞时,在后面排队的所有请求也一同被阻塞了,会招致客户端一直请求不到数据,这也就是「队头阻塞」。
http队首阻塞:简单理解就是需要排队,队首的事情没有处理完的时候,后面的人都要等着。
队头阻塞"与短连接和长连接无关,而是由 HTTP 基本的"请求 - 应答"机制所导致的。因为 HTTP 规定报文必须是"一发一收",这就形成了一个先进先出的"串行"队列。
而http的队首阻塞,在管道化和非管道化下,表现是不同的。
http1.0的队首阻塞 ( 非管道化下 ) :对于同一个tcp连接,所有的http1.0请求放入队列中,只有前一个请求的响应收到了,然后才能发送下一个请求,由下图可以看到,如果前一个请求卡着了,那么队列中后续的http就会阻塞。可见,http1.0的队首组阻塞在客户端。
http1.1的队首阻塞 ( 管道化下 ):对于同一个tcp连接,开启管道化后,http1.1允许一次发送多个http1.1请求,也就是说,不必等前一个响应收到,就可以发送下一个请求,这样就解决了http1.0的客户端的队首阻塞。但是,http1.1规定,服务器端的响应的发送要根据请求被接收的顺序排队,也就是说,先接收到的请求需要先响应回来。这样造成的问题是,如果最先收到的请求的处理时间长的话,响应生成也慢,就会阻塞已经生成了的响应的发送。也会造成队首阻塞,响应的阻塞。可见,应用管道化技术后,http1.1的队首阻塞发生在服务器端。
因此说,HTTP/1.1 管道解决了请求的队头阻塞,但是没有解决响应的队头阻塞。
解决http队头阻塞的方法:
1、并发TCP连接(浏览器一个域名采用6-8个TCP连接,并发HTTP请求),可以理解成增加了任务队列,不会导致一个任务阻塞了该任务队列的其他任务。
2、域名分片(多个域名,可以建立更多的TCP连接,从而提高HTTP请求的并发)可以在一个域名下分出多个二级域名出来,而它们最终指向的还是同一个服务器,这样子的话就可以并发处理的任务队列更多,也更好的解决了队头阻塞的问题。
3、HTTP2方式(多路复用特性)
HTTP1.1常见性能问题
- 延迟难以下降,虽然现在网络的「带宽」相比以前变多了,但是延迟降到一定幅度后,就很难再下降了,说白了就是到达了延迟的下限;
- 并发连接有限,谷歌浏览器最大并发连接数是6个,而且每一个连接都要经过 TCP 和 TLS握手耗时,以及TCP 慢启动过程给流量带来的影响;
- 队头阻塞问题,同一连接只能在完成一个 HTTP 事务(请求和响应)后,才能处理下一个事务;HTTP 头部巨大且重复,由于 HTTP 协议是无状态的,每一个请求都得携带 HTTP 头部,特别是对于有携带cookie 的头部,而 cookie 的大小通常很大;
- 不支持服务器推送消息,因此当客户端需要获取通知时,只能通过定时器不断地拉取消息,这无疑浪费大量了带宽和服务器资源。
为解决HTTP1.1性能问题而提出的常见优化手段
- 将多张小图合并成一张大图供浏览器 JavaScript来切割使用,这样可以将多个请求合并成一个请求,但是带来了新的问题,当某张小图片更新了,那么需要重新请求大图片,浪费了大量的网络带宽;
- 将图片的二进制数据通过 base64 编码后,把编码数据嵌入到 HTML或 CSS 文件中,以此来减少网络请求次数;
- 将多个体积较小的 JavaScript 文件使用 webpack 等工具打包成一个体积更大的 JavaScript 文件,以一个请求替代了很多个请求,但是带来的问题,当某个 js 文件变化了,需要重新请求同一个包里的所有 js 文件;
- 将同一个页面的资源分散到不同域名,提升并发连接上限,因为浏览器通常对同一域名的 HTTP 连接最大只能是6个。
总的来说: - 将多个小资源合并为一个大资源,从而将多个请求合并为一个请求,减少请求次数,带来的问题是其中一个资源变化却需要更新所有资源,占用了带宽;
- 将同一个页面的资源分散到不同的域名,提升并发连接上限,因为浏览器对同一域名的 HTTP 连接最大只能是6个。
HTTP/2
HTTP/2 协议是基于 HTTPS 的, 所以HTTP/2的安全性也是有保障的。
HTTP2协议还包括:流控制、流状态、依赖关系等。
1、兼容HTTP1.1
- 第一点,HTTP/2 没有在 URI里引入新的协议名,仍然用「http://」表示明文协议,用「https://」表示加密协议,于是只需要浏览器和服务器在背后自动升级协议,这样可以让用户意识不到协议的升级,很好的实现了协议的平滑升级。
- 第二点,只在应用层做了改变,还是基于 TCP 协议传输,应用层方面为了保持功能上的兼容,HTTP/2 把 HTTP 分解成了「语义」和「语法」两个部分,「语义」层不做改动,与 HTTP/1.1 完全一致,比如请求方法、状态码、头字段等规则保留不变。但是,HTTP/2 在「语法」层面做了很多改造,基本改变了 HTTP 报文的传输格式。
2、头部压缩
HTTP/2 使用 HPACK 压缩算法对请求和响应头部进行压缩,减少了传输的头部数据量,降低了延迟。
HPACK算法包含三个组成部分:
(i)静态字典
(ii)动态字典
(iii)Huffman编码(压缩算法)
客户端和服务器都会建立和维护【字典】,用长度较小的索引号表示重复的字符串,再用 Huffman 编码压缩数据。
为什么要压缩头部?
- 含很多固定的字段,比如Cookie、User Agent、Accept等,这些字段加起来也高达几百字节甚至上千字节,所以有必要压缩;
- 大量的请求和响应的报文里有很多字段值都是重复的,这样会使得大量带宽被这些冗余的数据占用了,所以有必要避免重复性;
- 字段是 ASCI 编码的,虽然易于人类观察,但效率低,所以有必要改成二进制编码。
3、二进制帧
二进制帧: HTTP/2 将数据分割成二进制帧进行传输,分为头信息帧(Headers Frame)和数据帧(Data Frame),增加了数据传输的效率。
HTTP2更厉害的地方在于: 将HTTP1的文本格式改成二进制格式传输数据,极大提高了HTTP传输效率,而且二进
制数据使用位运算能高效解析。
暂时无法在飞书文档外展示此内容
4、Stream并发传输(多路复用)
引出了 Stream 概念,多个 Stream 复用在一条 TCP 连接,针对不同的 HTTP 请求用独一无二的Stream ID 来区分,接收端可以通过 Stream lD 有序组装成 HTTP 消息,不同 Stream 的帧是可以乱序发送的,因此可以并发不同的 Stream ,也就是 HTTP/2 可以并行交错地发送请求和响应。
流标识符:用来标识该Frame属于哪个Stream。接收方可以根据这个信息从乱序的帧里找到相同Stream ID的帧,从而有序组装信息。
- HTTP1.1 基于请求-响应模型。同一个连接中,HTTP完成一个事务(请求与响应),才能处理下一个事务。即:再发出请求等待响应的过程中是没办法做其他事情的,会造成【队头阻塞】问题。
- HTTP2通过Stream这个设计(多个Stream复用一条TCP连接,达到并发的效果),解决了【队头阻塞】的问题,提高了HTTP传输的吞吐量。
- 1个 TCP 连接包含一个或者多个 Stream,Stream 是 HTTP/2 并发的关键技术;
- Stream 里可以包含1个或多个 Message,Message 对应 HTTP/1 中的请求或响应,由 HTTP 头部和包体构成,
- Message 里包含一条或者多个 Frame,Frame是 HTTP/2 最小单位,以二进制压缩格式存放 HTTP/1 中的内容(头部和包体)。包括头信息帧(Headers Frame)和数据帧(Data Frame)。
- HTTP消息可以由多个Frame构成。
- 一个Frame可以由多个TCP报文构成。
在HTTP2连接上,不同Stream的帧可以乱序发送(因此可以并发不同的Stream),接收端可以通过Stream ID 有序组装HTTP消息。
- HTTP/2 通过 Stream 实现的并发,比 HTTP/1.1 通过 TCP 连接实现并发要厉害的多,因为当 HTTP/2 实现 100 个并发 Stream 时,只需要建立一次 TCP 连接,而 HTTP/1.1 需要建立 100 个 TCP 连接,每个 TCP 连接都要经过TCP 握手、慢启动以及 TLS 握手过程,这些都是很耗时的。
- HTTP/2 还可以对每个 Stream 设置不同优先级,帧头中的「标志位」可以设置优先级,比如客户端访问HTML CSS 和图片资源时,希望服务器先传递 HTML/CSS,再传图片,那么就可以通过设置 Stream 的优先级来实现,以此提高用户体验。
5、服务器主动推送(存在TCP层的队头阻塞)
在 HTTP/2 中,服务器可以对客户端的一个请求发送多个响应,即服务器可以额外的向客户端推送资源,而无需客户端明确的请求。
但是 HTTP/2 仍然存在着队头阻塞的问题,只不过问题是在传输层。
HTTP/2 是基于 TCP 协议来传输数据的,TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整且连续的,这样内核才会将缓冲区里的数据返回给 HTTP 应用,那么当「前1个字节数据」没有到达时,后收到的字节数据只能存放在内核缓冲区里,只有等到这1个字节数据到达时,HTTP/2 应用层才能从内核中拿到数据,这就是 HTTP/2队头阻塞问题。
HTTP1.1不支持服务器主动推送资源给客户端,都是由客户端向服务器发起请求后,才能获取到服务器响应的资
源。在HTTP2中,客户端在访问HTML时,服务器可以直接主动推送CSS⽂件,减少了消息传递的次数。
客户端发起的请求,必须使用的是奇数号 Stream,服务器主动的推送,使用的是偶数号 Stream。服务器在推送资源时,会通过 PUSH_PROMISE 帧传输 HTTP 头部,并通过帧中的 Promised Stream ID 字段告知客户端,接下来会在哪个偶数号 Stream 中发送包体。
暂时无法在飞书文档外展示此内容
HTTP/3(解决TCP队头阻塞)
HTTP/2 队头阻塞的问题是因为 TCP,所以 HTTP/3 把 HTTP 下层的 TCP 协议改成了 UDP!
HTTP/3 基于 QUIC 协议,具有以下特点:
- 零 RTT 连接建立: QUIC(Quick UDP Internet Connections)允许在首次连接时进行零往返时间(Zero Round Trip Time)连接建立,从而减少了连接延迟,加快了页面加载速度。
- 无队头阻塞: QUIC 使用 UDP 协议来传输数据。一个连接上的多个stream之间没有依赖,如果一个stream丢了一个UDP包,不会影响后面的stream,不存在TCP 队头阻塞。
- 连接迁移: OUIC允许在网络切换(如从 Wi-Fi到移动网络)时,将连接迁移到新的 IP 地址,从而减少连接的中断时间。
- 向前纠错机制: 每个数据包除了它本身的内容之外,还包括了部分其他数据包的数据,因此少量的丢包可以通过其他包的冗余数据直接组装而无需重传。向前纠错牺牲了每个数据包可以发送数据的上限,但是减少了因为丢包导致的数据重传。
HTTP/2的缺点
HTTP2 协议是基于TCP实现的,所以存在三个缺陷:
- 队头阻塞:TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整且有序的,如果序列号较低的 TCP 段在网络传输中丢失了,即使序列号较高的 TCP 段已经被接收了,应用层也无法从内核中读取到这部分数据,从HTTP 视角看,就是请求被阻塞了。
- TCP与TLS的握手时延迟:发出HTTP请求时,需要经过TCP三次握手和TLS四次握手(TLS 1.2是4次,TLS 1.3是三次,开启Fast Open后可以和TCP同时进行),共计3 RTT的时延才能发出请求数据。
- 网络迁移需要重新连接:一个TCP连接由【源IP地址,源端口,目标IP地址,目标端口】确定。若IP地址或端口发生变暖,这需要重新进行连接。这不利于移动设备切换网络的场景。要解决该问题,就要修改传输层协议。在HTTP3中传输层协议修改为了 UDP。
QUIC协议的特点(将UDP变成可靠的)
UDP是一个简单的、不可靠的传输协议,而且UDP包之间是无序的,也没有依赖关系。UDP也不需要连接。
HTTP3基于UDP协议在应用层实现了 QUIC 协议,它有类似TCP的连接管理、拥塞窗口、流量控制的网络特性,相当于将不可靠的UDP协议变成可靠的了,无需担心数据包丢包的问题。
无队头阻塞
- HTTP2:只要某个流中的数据包丢失了,其他流也会因此受影响。
- HTTP3:流与流(Stream)之间不影响
更快的连接建立
对于 HTTP/1 和 HTTP/2 协议,TCP 和 TLS 是分层的,分别属于内核实现的传输层、openssl库实现的表示层,因此它们难以合并在一起,需要分批次来握手,先 TCP 握手,再 TLS 握手。
HTTP/3 在传输数据前虽然需要 QUIC协议握手,这个握手过程只需要1RTT,握手的目的是为确认双方的「连接ID」,连接迁移就是基于连接 ID 实现的。
但是 HTTP/3 的 QUIC 协议并不是与 TLS 分层,而是QUIC 内部包含了 TLS,它在自己的帧会携带 TLS 里的"记录"、再加上 QUIC 使用的是 TLS1.3,因此仅需1个 RTT 就可以「同时」完成建立连接与密钥协商,甚至在第二次连接的时候,应用数据包可以和 QUIC握手信息(连接信息+TLS 信息)一起发送,达到 0-RTT 的效果。
为什么QUIC只需要一个RTT就可以完成建立连接与密钥协商
首先,TCP的三次握手需要 1个RTT 来完成。
三次握手过程:
- 客户端发送SYN(Synchronize)报文:客户端向服务器发送一个SYN报文,用于请求建立连接。这是握手的第一步。
- 服务器响应SYN-ACK:服务器收到SYN报文后,回应一个SYN-ACK报文,表示同意建立连接,并确认收到了客户端的SYN。这是握手的第二步。
- 客户端发送ACK:客户端收到SYN-ACK后,发送一个ACK(Acknowledgment)报文,确认收到了服务器的响应。这是握手的第三步,完成了连接的建立。
时延解释:
- 发送SYN并接收到SYN-ACK:这一步耗费1个RTT(客户端到服务器再返回客户端)。
- 发送ACK:这一步通常不需要额外的RTT,因为在发送ACK后,客户端和服务器都已经可以开始发送数据了。
因此,TCP三次握手只需要 1个RTT 来完成连接的建立。
总结:而QUIC使用的是TLS 1.3 只需要三次握手,与TCP相同,因此如果它与TCP一起传输数据,可以看作只需要进行TCP连接,也就是只需要 1个RTT。
连接迁移
基于 TCP 传输协议的 HTTP 协议,由于是通过四元组(源 IP、源端口、目的IP、目的端口)确定一条 TCP 连接,那么当移动设备的网络从 4G 切换到 WIFI 时,意味着 IP 地址变化了,那么就必须要断开连接,然后重新建立连接,而建立连接的过程包含 TCP 三次握手和 TLS 四次握手的时延,以及 TCP 慢启动的减速过程,给用户的感觉就是网络突然卡顿了一下,因此连接的迁移成本是很高的。
而 QUIC 协议没有用四元组的方式来"绑定"连接,而是通过连接 ID来标记通信的两个端点,客户端和服务器可以各自选择一组 ID 来标记自己,因此即使移动设备的网络变化后,导致IP 地址变化了,只要仍保有上下文信息(比如连接 ID、TLS 密钥等),就可以"无缝"地复用原连接,消除重连的成本,没有丝毫卡顿感,达到了连接迁移的功能。
HTTP/3 同 HTTP/2 一样采用二进制帧的结构,不同的地方在于 HTTP/2 的二进制帧里需要定义 Stream,而HTTP/3 自身不需要再定义 stream,直接使用 QUIC 里的 Stream,于是 HTTP/3 的帧的结构也变简单了。