这里主要是对 HTTP 协议不同版本中队头阻塞问题的概述;思路是:
1.0 出现问题;1.1 改善问题-主流;2.0 解决问题;
个人笔记,难免有差错,敬请指正。
HTTP 1.0
无连接
HTTP 1.0,是一种无状态、无连接的应用层协议。其规定浏览器的每次请求都需要与服务器建立一个TCP连接,服务器处理完成后立即断开TCP连接(无连接),服务器不跟踪每个客户端也不记录过去的请求(无状态)。这可能就会引发一些问题。
首先,无连接的特性导致最大的性能缺陷就是无法复用连接。每次发送请求的时候,都需要进行一次TCP的连接,而TCP的连接释放过程又是比较费事的。这种无连接的特性会使得网络的利用率非常低。
其次就是队头阻塞(head of line blocking)。
队头阻塞
由于HTTP1.0规定下一个请求必须在前一个请求响应到达之后才能发送。假设前一个请求响应一直不到达(网络原因导致迟到或者丢失、服务器崩盘等原因),那么下一个请求就不发送,同样的后面的请求也给阻塞了。
其实,队头阻塞与短连接和长连接无关(这里只是有所关联,捎带介绍),而是由 HTTP 基本的 请求 - 应答 模型所导致的。因为 HTTP 规定报文必须是 一发一收,一个 request 对应一个 response,这就形成了一个先进先出的 串行 队列。队列里的请求没有轻重缓急的优先级,只有入队的先后顺序,排在最前面的请求被最优先处理。如果队首的请求因为处理的太慢耽误了时间,那么队列里后面的所有请求也不得不跟着一起等待,结果就是其他的请求承担了不应有的时间成本。
关于队头阻塞,在 HTTP1.0 协议中没有什么具体的解决办法,之后更新为 HTTP1.1 之后有所改善。
HTTP 1.1
长连接
HTTP1.1 增加了一个 Connection 字段,通过设置 Keep-Alive 可以保持 HTTP 连接不断开,避免了每次客户端与服务器请求都要重复建立释放建立 TCP 连接,提高了网络的利用率。如果客户端想关闭 HTTP 连接,可以在请求头中携带 Connection: false 来告知服务器关闭请求。
管道化
HTTP1.1 支持请求管道化(pipelining)。基于HTTP1.1的长连接,使得请求管线化成为可能。管线化使得请求能够“并行”传输。举个例子来说,假如响应的主体是一个 HTML 页面,页面中包含了很多img,这个时候 keep-alive 就起了很大的作用,能够进行“并行”发送多个请求。这里虽然不用等待上一个请求返回 response,可以连续发送多给请求,但一个 request 对应一个 response。(注意这里的“并行”并不是真正意义上的并行传输,具体解释如下。)
需要注意的是,服务器必须按照客户端请求的先后顺序依次回送相应的结果,以保证客户端能够区分出每次请求的响应内容。
也就是说,HTTP管道化可以让我们把先进先出队列从客户端(请求队列)迁移到服务端(响应队列)。
如图所示,客户端同时发了两个请求分别来获取 HTML 和 css,假如说服务器的 css 资源先准备就绪,服务器也会先发送 HTML 再发送 css。
换句话来说,只有等到 HTML 响应的资源完全传输完毕后,css 响应的资源才能开始传输。也就是说,不允许同时存在两个并行的响应。
可见,HTTP1.1 还是无法解决队头阻塞(head of line blocking)的问题。同时“管道化”技术存在各种各样的问题,所以很多浏览器要么根本不支持它,要么就直接默认关闭,并且开启的条件很苛刻…而且实际上好像并没有什么用处。
那我们在谷歌控制台看到的并行请求又是怎么一回事呢?
如图所示,绿色部分代表请求发起到服务器响应的一个等待时间,而蓝色部分表示资源的下载时间。按照理论来说,HTTP 响应理应当是前一个响应的资源下载完了,下一个响应的资源才能开始下载。而这里却出现了响应资源下载并行的情况。这又是为什么呢?
其实,虽然 HTTP1.1 支持管道化,但是服务器也必须进行逐个响应的送回,这个是很大的一个缺陷。实际上,现阶段的浏览器厂商采取了另外一种做法,它允许我们打开多个 TCP 的会话。也就是说,上图我们看到的并行,其实是不同的 TCP 连接上的HTTP请求和响应。这也就是我们所熟悉的浏览器对同域下并行加载6~8个资源的限制。而这,才是现在的主流解决方式,才是真正的并行!
HTTP 2.0
二进制分帧
HTTP2.0通过在应用层和传输层之间增加一个二进制分帧层,突破了HTTP1.1的性能限制、改进传输性能。
可见,虽然HTTP2.0的协议和HTTP1.x协议之间的规范完全不同了,但是实际上HTTP2.0并没有改变HTTP1.x的语义。简单来说,HTTP2.0只是把原来HTTP1.x的header和body部分用frame重新封装了一层而已。
多路复用
概念
- 流(stream):已建立连接上的双向字节流。
- 消息:与逻辑消息对应的完整的一系列数据帧。
- 帧(frame):HTTP2.0通信的最小单位,每个帧包含帧头部,至少也会标识出当前帧所属的流(stream id)。
从图中可见,所有的HTTP2.0通信都在一个TCP连接上完成,这个连接可以承载任意数量的双向数据流。
每个数据流以消息的形式发送,而消息由一或多个帧组成。这些帧可以乱序发送,然后再根据每个帧头部的流标识符(stream id)重新组装。
举个例子,每个请求是一个数据流,数据流以消息的方式发送,而消息又分为多个帧,帧头部记录着 stream id 用来标识所属的数据流,不同属的帧可以在连接中随机混杂在一起。接收方可以根据 stream id 将帧再归属到各自不同的请求当中去。
另外,多路复用(连接共享)可能会导致关键请求被阻塞。HTTP2.0 里每个数据流都可以设置优先级和依赖,优先级高的数据流会被服务器优先处理和返回给客户端,数据流还可以依赖其他的子数据流。
可见,HTTP2.0 实现了真正的并行传输,它能够在一个 TCP 上进行任意数量 HTTP 请求。而这个强大的功能则是基于 二进制分帧 的特性。