这一篇文章,我们核心要聊的事情就是HTTP的对头阻塞问题,因为HTTP的核心改进其实就是在解决HTTP的队头阻塞。所以,我们会讲的理论多一些,而实践其实很少,要学习的头字段也只有一个,我会在最开始就讲完这个头字段,然后我们安心的去学习接下来的理论知识,嗯……这些理论知识很重要。

  那我们就先来看看我们本篇要学的这个唯一的头字段是什么吧。

一、Connection头字段及其示例

  其实在聊这个字段之前,我们要先学一些前置知识才好,但是我想先讲这个字段,后面再带着疑问去学理论,嗯……就这样。

  Connection字段其实很好理解,就是用来开启长链接的,长链接的意思就是会重复利用TCP开启的通道,不会在请求一次后关闭。长链接可以这样开启Connection: keep-alive。这个东东在HTTP/1.1中是默认开启的,也就是说你啥也不干它就开启,当然,如果你想关闭的话可以这样Connection: close

  不仅仅如此,客户端和服务器都可以通过Keep-alive: timeout=value来限定长链接的超时时间,但是服务器和客户端往往都不一定遵守,约束力并不是很强。大家了解下就好了,另外,一些代理服务器比如Nginx,也会针对该字段有一些特殊的策略,比如该通道多长时间没有发送数据就关闭,比如该通道发送了多少次数据后就关闭等等。

  那么下面我们就来看看具体的例子,来实践一下。客户端和服务器的代码都很简单,基本的代码在上一篇文章中都接触过,我就不再复制代码了,可以在这里看。我们直接看下请求的结果。

  关于Connection的有三个字段,其中Proxy-Connection从词意上来讲就是指代理的通道连接方式。然后你看,Connection是Keep-alive,Keep-Alive字段的超时时间设置为4,也就是默认设置了四秒没有在该通道上传输数据就关闭TCP通道。那怎么验证呢?我们还得用下Wireshark。先请求下数据,然后看看四秒后会不会有TCP的四次挥手断开连接。

  我们可以清楚的看到三次握手后(也就是96、97、98三个id的tcp)的HTTP请求,过了四秒,就四次挥手(就是344、345、346、347四次)断开连接了,大家有兴趣自行尝试体验一下。

  例子就这么简单~我们接下来要学习理论知识了,这些理论知识很重要,这个例子就当个开胃小菜吧。

二、长短连接说短长

  我在上面的例子中说到,HTTP/1.1会默认开启长链接,那为什么要开启长链接?什么是长链接?那既然有长链接是不是还有短连接呢?嗯……你听我慢慢说。

一)短连接是什么?

  我们知道,HTTP/0.9和HTTP/1.0都是十分简单的协议,它的底层是基于TCP的,在每次请求发送前都需要通过三次握手来和服务器建立连接,响应结束后会通过四次挥手断开连接。这就是短连接,在早期的时候也会被称为是无连接的。而这种操作是十分浪费资源的,效率就很低下。

  为什么早期的HTTP会是这个样子呢?因为在当时,大家知道大多数的网站都是静态页面,一个页面上能放几个gif图那都是很酷炫的事情了。所以,在这样的场景下,没有那么多的请求需要,这样设计似乎也无可厚非,但是随着互联网页面的极速发展,一个页面可能有几个甚至几十个请求,几百个外部资源文件,每次都开启、关闭、开启、关闭,浪费的资源可不是一点半点。

  所以,我们就需要对短连接进行改进,于是长连接出现了。

二)时代的宠儿:长连接

  因为短连接实在是无法适应时代的需要,太浪费了,所以为了解决短连接带来问题,在HTTP/1.1中就增加了持久链接的方法,它的特点就是可以在一个TCP连接上传输多个HTTP请求,只要浏览器或者服务器没有明确断开,那么就会一直保持连接状态。虽然这样做并没有改善TCP的连接效率,但是由于开启和断开的次数少了,把整个开启和断开的时间平均到了多次请求中,每个请求和应答的无效时间就少了很多,从而增加了整体传输的效率。

  在目前的浏览器中,同一个域名可以开启六个TCP连接,不过这里稍微要注意的是,其实HTTP规范约定的TCP连接数量是2个,但是各大浏览器厂商觉得肯定不够用,所以在实现层面上来说,每个域名可以开启6个TCP连接,HTTP规范在新的版本RFC7230中也就顺水推舟,约定可以是6到8个连接。

  那如果六个TCP连接还是不够用呢?嗯……我们可以多开几个域名,比如a.zaking.com,b.zaking.com,c.zaking.com,每一个域名都指向同一台服务器,说白了就是用数量来解决质量的方式,而这种解决思路也有个高大上的名词,叫做“域名分片”。

三、初识队头阻塞

  队头阻塞是本篇的重点,也是一件比较有趣的事情,有趣在哪里呢?因为它解决不了。我们下面就来看看什么是队头阻塞。

  因为HTTP是基于“请求—应答”模型的,在这个模型的基础上,HTTP规定报文必须是一发一收的,这就形成了一个先进先出的串行队列,如果你不知道什么是队列的话,请看这里。既然是队列,就存在一个这样的问题,队列里的请求没有优先级,谁先进来谁就先出去。但是假如某一个排在前面的请求卡住了,没有返回,那后面的所有队列中的请求都要等着那个卡住的请求结束,结果就是我分担了本来不应该由我来承担的时间损耗。

  那要怎么解决这个问题呢?诶?你不是说这个问题是解决不了的么?嗯……从规范上,从设计上来说确实无法解决,既然是队列就必然是这样的,但是上有政策下有对策,我大不了多开几个域名呗,多开几个队列,让它堵的可能性小点,你是不是想到了啥?嗯,就是我们上面说到的“域名分片”技术。

  其实很好理解,就好像我们在一个汽车在单车道上跑,堵车的可能性就很大,堵车了我也没办法,只能等前面解决了继续走,但是假设我是6车道,18车道,是不是就能在一定程度上解决这个问题了。

  当然,你并没有从根本解决队头阻塞。只是使了点小手段罢了。

  我在demo代码里写了点小例子,大家可以点击试试。坦白说我并不知道底层的实现是什么,但是大概能猜到原因。

  当你发送很多无响应的HTTP请求后,等一会,再点有响应的HTTP请求,你会发现卡死了,我猜就是因为那些无响应的HTTP请求占用全部六个TCP连接。当然,你也可以通过Wireshark来验证这一点。不多说啦~

  完了嘛?还没……

  在HTTP/1.1中,也曾试图通过“管线化” 的技术来解决队头阻塞的问题,管线化就是指将多个HTTP请求整批发送给服务器的技术,虽然可以整批发送,但是服务器还是要按照队列的顺序返回结果,得~~~白玩了。所以最后这玩意没啥用,大家了解下就行了

四、多路复用的HTTP/2

  我们回顾一下上面的三个部分,发现HTTP/1.1为了优化做了哪些努力,一个是长连接,一个是每个域名可以同时维护6个TCP长连接,一个是就是域名分片技术。一共三种,但是这些手段都没有从根本上解决队头阻塞的问题,HTTP数据报文在传输的某一条连接上堵塞了,还是要等待,没办法。

  虽然使用这些手段一定程度上缓解了HTTP/1.0和HTTP/0.9所带来的问题,但是其实问题还是不少的,性能还可以进一步的压缩。

  其中关于TCP的问题有慢启动问题,以及带宽竞争问题。在TCP进入到传输数据的状态时,会处于一种递增的状态,就像开车一样,缓慢的从0加速到多少时速,这样做是为了减少网络拥塞,但是有些数据本身就很小,等着你慢慢启动就很浪费时间。

  而带宽竞争,则是指当带宽充足的时候,每条连接都会缓慢的增加发送速度,而一旦带宽不足时,这些连接传输数据的速度则会减慢,这样就回带来一个问题,就是优先级的问题,重要资源随着普通资源一起减慢了,真的是很苦恼。

  这两个问题是TCP引起的,HTTP想改变也改变不了,只能接受,所以HTTP/3的时候干脆不用TCP了。

  但是,队头阻塞的问题,则是HTTP可以进一步优化和解决的,想办法在一定程度上规避TCP的这两个问题,什么意思呢?

  HTTP/2的思路是一个域名只采用一个TCP连接,这样就能尽可能的减少TCP的慢启动和带宽竞争问题,就一个TCP连接,你也不用竞争了,就一个TCP连接,你就算启动的很慢,平分到每一个连接好像也还可以。你看,好像所有的解决思路都类似,解决不了就平分。

  基于这样的思路,HTTP/2针对队头阻塞的问题提出了多路复用的解决方案。什么意思呢,HTTP/2实现了资源的并行请求,也就是你在任何时候都可以发送请求,不用管前一个请求是否堵塞,服务器会在处理好数据后就返回给你。

  那,核心的问题来了,多路复用是如何实现的呢?

多路复用的实现

  HTTP/2在HTTP和TCP的中间又加了一层,也就是二进制分帧层:

  就像上图这样,其实二进制分帧层属于应用层,这个二进制分帧层做了什么呢?就是把发送的HTTP数据包拆成一个一个带有id的帧,服务器收到这些帧后,会把有同一个id的帧合并成一条完整的信息,那么同样的,服务器发送给客户端的数据也要这样经过二进制分帧层的分帧处理,浏览器会根据对应的id发送给请求的数据源头。

  HTTP/2就是通过这样的形式,引入了多路复用的机制,来解决队头阻塞的问题。

  看起来似乎很美好了是吧,HTTP/2可以说是HTTP目前为止最完美的解决方案了。但是故事并没有就此停止。

五、TCP也有队头阻塞

  虽然,HTTP/2解决了HTTP的队头阻塞,但是TCP也有队头阻塞,虽然你把HTTP数据包拆分成了一个又一个的帧,但是你还是传输在一条通道上,一旦某一个数据帧丢失了,那TCP就得等丢失的数据包重新传过来才行,卧槽,问题又回到了原点。那咋整?我们改一改TCP协议?

  抱歉,你改不了,主要的原因在于僵化,一个是中间设备的僵化,一个是操作系统的僵化。

  中间设备其实就是指数据在互联网中传输的过程中,所遇到的各种设备,比如路由器,网关,代理服务器,服务器等等等等,很多很多,这些东西比较硬性,一旦安装软件后很少升级,所以你改了客户端的TCP,这一连串的设备,甚至说全球的设备都要改,你想想,是不是很夸张。

  而操作系统僵化,则是因为TCP的核心实现是由操作系统底层来处理的,所以你看,要改TCP就要改操作系统,想想就头大。

  所以,由于僵化的原因,TCP改不了。那咋整?嗯……那就不用他了呗,我们用UDP好了。

  HTTP/3选择用UDP作为传输协议,并且在UDP和HTTP/3中又加了QUIC层。QUIC层则针对UDP区别于TCP的一些特性进行了处理,从而让UDP的传输像TCP一样完整和安全,并且像HTTP/2那样采用多路复用机制,来解决TCP的队头阻塞。

  关于QUIC或者HTTP/3的更多内容,会在后面HTTP/3的部分详细讲解,本篇就不再过多的阐述了。

  嗯……本篇结束了~

六、总结

  这篇文章并不长,理论知识稍微多一点,而其中最核心的点就是队头阻塞和多路复用,大家一定要着重学习。那么在本篇的最后,留给大家两个小问题。

  1. 长连接和短连接是啥?长连接出现的原因是什么?解决了什么问题呢?
  2. 关于HTTP的队头阻塞,你都有哪些了解?HTTP解决了队头阻塞的问题么?如果解决了,又是如何解决的?如果没解决,为什么没解决呢?

真正“搞”懂HTTP协议07之队头阻塞真的很烦人的更多相关文章

  1. 真正“搞”懂HTTP协议03之时间穿梭

    上一篇我们简单的介绍了一下DoD模型和OSI模型,还着重的讲解了TCP的三次握手和四次挥手,让我们在空间层面,稍稍宏观的了解了HTTP所依赖的底层模型,那么这一篇,我们来追溯一下HTTP的历史,看一看 ...

  2. 真正“搞”懂HTTP协议02之空间穿梭

    时隔四年,这个系列鸽了四年,我终于觉得我可以按照自己的思路和想法把这个系列完整的表达出来了. 想起四年前,那时候还是2018年的六月份,那时候我还工作不到两年,那时候我翻译了RFC2616的部分内容, ...

  3. 【Http】队头阻塞(Head of line blocking)多路复用(Multiplexing)

        图中第一种请求方式,就是单次发送request请求,收到response后再进行下一次请求,显示是很低效的. 于是http1.1提出了管线化(pipelining)技术,就是如图中第二中请求方 ...

  4. 真正“搞”懂http协议01—背景故事

    去年读了<图解HTTP>.<图解TCP/IP>以及<图解网络硬件>但是读了之后并没有什么深刻的印象,只是有了一层模糊的脉络,刚好最近又接触了一些有关http的相关内 ...

  5. 搞懂Redis协议RESP

    RESP (REdis Serialization Protocal) Redis客户端和服务端之间通信的协议.它很简单,建立在TCP协议上,提供简单.高性能.可读性强的数据序列化的规范和语义. 5种 ...

  6. 【http】http协议的队首阻塞

    1 队首阻塞 就是需要排队,队首的事情没有处理完的时候,后面的人都要等着. 2 http1.0的队首阻塞 对于同一个tcp连接,所有的http1.0请求放入队列中,只有前一个请求的响应收到了,然后才能 ...

  7. http协议的队首阻塞

    1 队首阻塞 就是需要排队,队首的事情没有处理完的时候,后面的人都要等着. 2 http1.0的队首阻塞 对于同一个tcp连接,所有的http1.0请求放入队列中,只有前一个请求的响应收到了,然后才能 ...

  8. 搞懂分布式技术4:ZAB协议概述与选主流程详解

    搞懂分布式技术4:ZAB协议概述与选主流程详解 ZAB协议 ZAB(Zookeeper Atomic Broadcast)协议是专门为zookeeper实现分布式协调功能而设计.zookeeper主要 ...

  9. 搞懂分布式技术2:分布式一致性协议与Paxos,Raft算法

    搞懂分布式技术2:分布式一致性协议与Paxos,Raft算法 2PC 由于BASE理论需要在一致性和可用性方面做出权衡,因此涌现了很多关于一致性的算法和协议.其中比较著名的有二阶提交协议(2 Phas ...

  10. [转帖]USB-C和Thunderbolt 3连接线你搞懂了吗?---没搞明白.

    USB-C和Thunderbolt 3连接线你搞懂了吗? 2018年11月25日 07:30 6318 次阅读 稿源:威锋网 3 条评论 按照计算行业的风潮,USB Type-C 将会是下一代主流的接 ...

随机推荐

  1. MPI实现并行奇偶排序

    奇偶排序 odd-even-sort, using MPI 代码在 https://github.com/thkkk/odd-even-sort 使用 MPI 实现奇偶排序算法, 并且 MPI 进程 ...

  2. [Mysql] 页结构

    什么是页? 页是InnoDB中管理数据的最小单元 页与页之间是通过一个双向链表连接起来. 页的组成 FileHeader 上一页下一页的指针 FIL_PAGE_PREV FIL_PAGE_NEXT P ...

  3. Codeforces Round #831 (Div. 1 + Div. 2) A-E

    比赛链接 A 题解 知识点:数学. \(2\) 特判加 \(7\),其他加 \(3\) 直接偶数. 时间复杂度 \(O(1)\) 空间复杂度 \(O(1)\) 代码 #include <bits ...

  4. "xxx cannot be cast to jakarta.servlet.Servlet "报错解决方式

    在做jsp的上机时候同学出现了一个500错误:com.kailong.servlet.ComputeBill cannot be cast to jaka.servlet.Servlet 然后因为我用 ...

  5. Codeforces Round #805 (Div. 3)E.Split Into Two Sets

    题目链接:https://codeforces.ml/contest/1702/problem/E 题目大意: 每张牌上面有两个数字,现在有n张牌(n为偶数),问能否将这n张牌分成两堆,使得每堆牌中的 ...

  6. 使用idea创建第一个Mybatis程序及可能遇到的问题

    第一个Mybatis程序 思路:搭建环境->导入Mybatis->编写代码->执行 搭建环境 创建数据库 CREATE DATABASE `mybatis` USE `mybatis ...

  7. 嵌入式-C语言基础:通过结构体指针访问结构体数组

    #include<stdio.h> #include<string.h> struct Student { char name[32]; int age; int height ...

  8. Ajax(下)

    跨域 跨域的概念:非同源请求,均为跨域.如果两个页面拥有相同的协议(protocol),端口(port)和主机(host),那么这两个页面就属于同一个源(origin). 例如:主机:http://w ...

  9. centos7 ftp服务搭建记录

    1. 装包与卸载 yum -y install vsftpd yum -y autoremove vsftpd&&rm -rf /etc/vsftpd /etc/pam.d/vsftp ...

  10. 【Java并发001】使用级别:线程相关知识

    一.前言 本文介绍Java线程相关知识(不包括线程同步+线程通信,这个内容在笔者的另一篇博客中介绍过了),包括:线程生命周期.线程优先级.线程礼让.后台线程.联合线程. 二.线程生命周期 2.1 引子 ...