解决HTTP队头阻塞

879 阅读3分钟

HTTP/1.1协议都是“请求-应答”模型,规定必须是“一发一收”,这就形成了一个先进先出的“串行”队列。队列中的请求没有优先级,只能按照入队的先后时间排序,排在最前面的请求被最优先处理。

如果队首的请求处理的太慢耽误了时间,那么队列后面的所有请求也不得不一起等待,结果就是其他的请求承担了不必要的等待时间成本。 image.png

一、HTTP/1.1如何优化“队头阻塞”问题

HTTP/1.1的“请求-应答”模型无法改变,所以“队头阻塞”问题无法根本解决,只能缓解优化。

HTTP/1.1中采用“并发连接”来优化一个连接中队列阻塞的问题,当一个队列阻塞了就可以改换到其他不阻塞的队列中,用数量来解决效率的问题。

这种方式也存在缺陷。如果每个客户端都想要自己快,建立很多个连接,用户数 * 并发连接数 就会是一个天文数字,服务器的资源根本就扛不住,可能被认为是恶意攻击,反而会造成“拒绝服务”。

因此HTTP协议建议客户端使用并发,但不能“滥用”并发,一般浏览器把并发数控制在6~8。但“并发连接”压榨的性能依然无法满足互联网快速的发展,这时就出现了“域名分片”技术,使用多个域名来解决连接数量限制的问题,只是这些域名都指向同一台服务器。

二、HTTP/2在应用层解决“队头阻塞”问题

HTTP/2 建立在"HPack","Stream","TLS1.2"基础之上,比HTTP/1、HTTPS都要复杂一些。 image.png HTTP/2把原来HTTP/1的“Header+Body”的消息,“打散”为数个二进制帧(Frame)进行传输,用“Headers”帧存放头数据,“DATA”桢存放实体数据。这种消息的“碎片”到达目的地后应该怎么组装起来?

HTTP/2为此定义了一个“”(Stream)的概念,它是二进制帧的双向传输序列,同一个消息往返的帧会分配一个唯一的流ID。因为“流”是虚拟的,实际并不存在,所以HTTP/2就可以在一个TCP连接上用“流”同时发送多个“碎片化”的消息,这就是常说的“多路复用”——多个往返通信都复用一个连接来处理。

在“流”的层面上看,消息是一些有序的“帧”序列,而在“连接”的层面上来看,消息却是乱序收发的“帧”。多个请求/响应之间没有了顺序关系,不需要排队等待,也就不会再出现“队头阻塞”问题,降低了延迟,大幅度提高了连接的利用率。 image.png

三、HTTP/3在TCP层解决“队头阻塞”问题

HTTP/2虽然使用“帧” “流” “多路复用”,没有了“队头阻塞”,但这些手段都是在应用层,而在下层,也就是TCP协议里,还是会发生“队头阻塞”。

HTTP/2把多个“请求-响应”分解成流,交给TCP后,TCP会再拆成更小的包依次发送(segment)。在网络良好的情况下,包可以很快送达目的地。但如果网络质量比较差,就可能会丢包。而TCP为了保证可靠传输,有个特别的“丢包重传”机制,丢失的包必须等待重新传输确认,其他的包即使已经收到了,也只能放在缓冲区里,上层的应用拿不出来。

HTTP/3在HTTP/2的基础上又实现了质的飞跃,真正“完美”地解决了“队头阻塞”问题。

image.png QUIC把下层的TCP“抽掉”了,换成了UDP。因为UDP是无序的,包之间没有依赖关系,所以就从根本上解决了“队头阻塞”。

QUIC选定了UDP,在它之上把TCP的那一套连接管理,拥塞窗口,流量控制等复制过来,“去其糟粕,取其精华”,打造出了一个全新的可靠传输协议,称为“新时代TCP”。