HTTP队头阻塞

1,989 阅读7分钟

HTTP长连接

说起队头阻塞, 就不得不先说一下HTTP 1.0版本到1.1版本的过度, 熟悉HTTP协议的小伙伴或许都知道, 这两个版本有不少区别, 为了节省篇幅, 这里只介绍其中一个:

keep-alive长连接:

我们都知道HTTP协议是建立在TCP/IP协议基础之上的, 所以为了减少3次握手和4次挥手过多导致的不必要开销, 所以要尽可能地复用当前的TCP连接, 以减少TCP建立连接和关闭链接产生的开销. 其实HTTP协议在1.0版本在请求头已经有了keep-alive字段以建立长连接, 其实最大的区别就是1.0需要主动告诉服务器需要建立长连接, 而1.1版本默认支持长连接.

下面简单介绍一下长连接的作用和工作机制, 以便理解后续的内容:

image.png

如上图所示, 在没有使用长连接功能时, 客户端每一次发送请求都需要经历这样一个过程:

  1. 经过三次握手, 建立TCP连接;
  2. 建立成功后, 客户端向服务器发送请求;
  3. 服务器接收到请求后, 对本次请求做出响应;
  4. 四次挥手断开TCP连接;

如果我们客户端有多个请求的话, 就需要建立多次的TCP连接, 那么如果我们发送一百个请求呢, 那岂不是就要经历一百次TCP连接与断开. 这么做无疑开销是巨大的, 不由得思考一下, 如何解决这个问题.

其实keep-alive就是为了解决这个问题而生的, 那么我们客户端和服务器建立TCP连接时, 可以通过keep-alive对当前TCP连接进行复用.

http1.1请求链接过程,如下图:

image.png

队头阻塞

我们在看上面那幅图的时候, 有没有发现什么问题?

首先我们把上面的请求分为三个HTTP请求, 我将上面的请求编上序号:

image.png

当我们在发送 1号 请求的时候得到了正常响应, 之后我们向服务器发送 2号请求时, 由于网络带宽或者种种原因, 导致发送请求到服务器缓慢或者服务器对其响应缓慢就会形成等待状态, 由于客户端请求是以队列形式发送到服务器, 所以二号请求发送后, 迟迟未收到响应, 所以导致 3号 请求需要等待二号请求完成后才能发送.

这就是著名的队头阻塞.

我们来看下维基百科是怎么解释的:

队头阻塞(英語:Head-of-line blocking,缩写:HOL blocking)在计算机网络的范畴中是一种性能受限的现象。 它的原因是一列的第一个数据包(队头)受阻而导致整列数据包受阻。 例如它有可能在缓存式输入的交换机中出现,有可能因为传输顺序错乱而出现,亦有可能在HTTP流水线中有多个请求的情况下出现。

其实不难发现, 虽然TCP连接是复用的, 但是请求还是逐个去请求的, 随意很容易造成阻塞问题.

那么对于这个问题, 我们该如何解决呢?

为了提高速度和效率,在持久连接的基础上,HTTP1.1进一步地支持在持久连接上使用管道化(pipelining)特性。管道化允许客户端在已发送的请求收到服务端的响应之前发送下一个请求,借此来减少等待时间提高吞吐,如果多个请求能在同一个TCP分节发送的话,还能提高网络利用率,流程如图:

image.png

如图可以看出, 三个客户端发出的请求, 不必等待上一个请求响应后才发送下一个, 可以进行并发请求.

虽然解决了由于上一个请求没有响应对下一次请求的发送造成了阻塞问题, 但是并没有完全的解决这个问题, 因为这种方式只解决了客户端的问题, 还是没有解决服务端的问题. 服务端还是要以FIFO的形式进行返回, 也就是说如果 1号 请求的响应造成阻塞, 二号请求也要等待一号请求处理完并且返回后才会响应给客户端.

说到这里不免有些小伙伴会有疑惑, 服务器为什么要这么处理呢?

其实使用HTTP管道化还有一些限制:

1、管道化要求服务端按照请求发送的顺序返回响应(FIFO),原因很简单,HTTP请求和响应并没有序号标识,无法将乱序的响应与请求关联起来。

2、当客户端在支持管道化时需要保持未收到响应的请求,当连接意外中断时,需要重新发送这部分请求。如果这个请求只是从服务器获取数据,那么并不会对资源造成任何影响,而如果是一个提交信息的请求如post请求,那么可能会造成资源多次提交从而改变资源,这是不允许的。而不会对服务器资源产生影响的请求有个专业名词叫做幂等请求。客户端在使用管道化的时候请求方式必须是幂等请求(什么是幂等请求自行学习吧, 不是本文重点, 不赘述)。

不支持管道化和管道化对比图放在一起, 大家进行对比一下:

image.png

如何解决

由于管道化本身会导致队头阻塞问题, 所以现代浏览器都默认关闭了这个功能.

HTTP协议建议大家并发使用TCP连接, 这里的并发指的是TCP并发连接. RFC2616 里明确限制每个客户端可以建立两个长连接,这里着重说明一下,客户端建立长连接的个数是针对域名发起的,举例说明,当我们访问a.com网站的时候,客户端与a.com服务器建立的长链接就是2个。

但是一般浏览器会把并发链接数增加到6到8个,谷歌浏览器是6个,也就是页面中如果针对同一个域名有多个http请求,谷歌浏览器会针对这个域名建立6个tcp长连接,在每个长连接里面再去处理http请求,但是这种方案其实对服务器的挑战非常大,有些web优化方案中还会突破6到8的限制,那就是域名切片,因为长连接是针对的同一个域名,那么如果开发人员将资源分布在不同的域名上,那么长连接的数量也是可以被突破的。

假设页面中有100张图片,基于这个案例,咱们用图示将http1.0到http1.1的变迁用三张图来表示一下:

image.png

http1.0时代:100个http请求建立100个tcp连接。

http1.1时代,tcp支持了长连接,每个tcp可以处理多个http请求。

总结

下面来总结一下:

第一部分我们介绍了什么是HTTP长连接, 主要是应用TCP长连接减少建立TCP连接和断开产生的开销.

第二部分, 我们介绍了什么是队头阻塞, 主要是由于请求队列中, 之前的请求没有结束, 后面的请求无法处理导致的阻塞.

第三部分我们介绍了一下如何解决此类问题, 例如通过并发TCP连接, 由于客户端限制TCP连接数, 所以可以对域名切片, 从而突破这个限制.

以上就是我对队头阻塞一些粗浅的理解, 如果哪里有表达不准去的, 欢迎评论区指出, 我会及时改正.