HTTP 队头阻塞
HTTP/1.1 的队头阻塞
问题:HTTP/1.1 是一个纯文本协议,它只在有效荷载(payload)的前面附加头(headers),在资源块(resource chunks)之间不使用分隔符。它不会进一步区分单个资源与其他资源。HTTP 规定报文必须是“一发一收”,这就形成了一个先进先出的串行队列。
比如:当浏览器发送给服务器的资源包括:js(大资源块)、css(小资源块)等内容,但是服务器不能对他们进行分块解析,就会导致需要等到js块解析完毕后,才去解析css块。称为http队头阻塞。
一种解决办法,浏览器打开许多并行TCP连接,但这既不高效,也不可扩展。
HTTP/2(基于 TCP)的队头阻塞
目标:回到单个 TCP 连接,正确地复用资源块,解决http队头阻塞问题。
解决方案:在资源块之前添加了数据帧(DATA frame),标识每个资源块属于哪个“流”(stream)。这些数据帧主要包含两个关键的元数据。首先:下面的块属于哪个资源。每个资源的“字节流(bytestream)”都被分配了一个唯一的数字,即流id(stream id)。第二:块的大小是多少。
这样报文到达服务端之后,服务端应用可以区分哪些属于同一个资源快,进而可以进行复用。即:解决了“应用层”队头阻塞。
另外的问题还有”tcp层“队头阻塞:
假设包3先到达,服务器端必须等待包2到达后,才将其和包3送给浏览器。(HTTP/2 主要的问题在于,多个 HTTP 请求在复用一个 TCP 连接,下层的 TCP 协议是不知道有多少个 HTTP 请求的。所以一旦发生了丢包现象,就会触发 TCP 的重传机制,这样在一个 TCP 连接中的所有的 HTTP请求都必须等待这个丢了的包被重传回来)。
即:如果一个 TCP 包丢失,所有后续的包都需要等待它的重传,即使它们包含来自不同流的无关联数据。
和http1.1主要区别:
二进制传输:
在HTTP2.0中引入了新的编码机制,所有传输的数据都会被分割,并采用二进制格式编码。区别于基于文本的方式传输。
多路复用:
帧是最小的数据单位,每个帧会标识出该帧属于哪个流,流是多个帧组成的数据流。所谓多路复用,即在一个TCP连接中存在多个流,即可以同时发送多个请求。
HTTP/3(基于 QUIC)的队头阻塞
由于tcp本身的限制,难以对其进行改变,使其具有”流“的意识。
选择的替代方法是以 QUIC 的形式实现一个全新的传输层协议。它运行在不可靠的 UDP 协议之上。但它包括 TCP 的所有特性(可靠性、拥塞控制、流量控制、排序等),且集成了TLS,不允许未加密的连接。故创建了 HTTP/3,运行在QUIC协议上。
QUIC 的流帧(STREAM frames)分别跟踪每个流的字节范围。
QUIC协议知道有自己独立的流。
比如:当服务器端的包3比包2早到达,QUIC查看流1的字节范围,发现这个流帧(STREAM frame)完全遵循流id 1的第一个流帧,它可以立即将这些数据提供给浏览器进行处理。然而,对于流id 2,QUIC确实看到了一个缺口(它还没有接收到字节0-299,这些字节在丢失的 QUIC 数据包2中)。它将保存该流帧(STREAM frame),直到 QUIC 数据包2的重传(retransmission)到达。
不太深入的来看,QUIC 确实解决了 TCP 的队头阻塞。
补充
http1.x采用长连接(Connection:keep-alive),可以在一个TCP请求上,发送多个http请求。
有非管道化和管道化,两种方式:
非管道化:完全串行执行,请求->响应->请求->响应…,后一个请求必须在前一个响应之后发送。
管道化:请求可以并行发出,但是响应必须串行返回。后一个响应必须在前一个响应之后。原因是,没有序号标明顺序,只能串行接收。即客户端可以并行,服务端串行。客户端可以不用等待前一个请求返回,发送请求,但服务器端必须顺序的返回客户端的请求响应结果。