真正“搞”懂HTTP协议07之队头阻塞真的很烦人
这一篇文章,我们核心要聊的事情就是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的部分详细讲解,本篇就不再过多的阐述了。
嗯……本篇结束了~
六、总结
这篇文章并不长,理论知识稍微多一点,而其中最核心的点就是队头阻塞和多路复用,大家一定要着重学习。那么在本篇的最后,留给大家两个小问题。
- 长连接和短连接是啥?长连接出现的原因是什么?解决了什么问题呢?
- 关于HTTP的队头阻塞,你都有哪些了解?HTTP解决了队头阻塞的问题么?如果解决了,又是如何解决的?如果没解决,为什么没解决呢?
真正“搞”懂HTTP协议07之队头阻塞真的很烦人的更多相关文章
- 真正“搞”懂HTTP协议03之时间穿梭
上一篇我们简单的介绍了一下DoD模型和OSI模型,还着重的讲解了TCP的三次握手和四次挥手,让我们在空间层面,稍稍宏观的了解了HTTP所依赖的底层模型,那么这一篇,我们来追溯一下HTTP的历史,看一看 ...
- 真正“搞”懂HTTP协议02之空间穿梭
时隔四年,这个系列鸽了四年,我终于觉得我可以按照自己的思路和想法把这个系列完整的表达出来了. 想起四年前,那时候还是2018年的六月份,那时候我还工作不到两年,那时候我翻译了RFC2616的部分内容, ...
- 【Http】队头阻塞(Head of line blocking)多路复用(Multiplexing)
图中第一种请求方式,就是单次发送request请求,收到response后再进行下一次请求,显示是很低效的. 于是http1.1提出了管线化(pipelining)技术,就是如图中第二中请求方 ...
- 真正“搞”懂http协议01—背景故事
去年读了<图解HTTP>.<图解TCP/IP>以及<图解网络硬件>但是读了之后并没有什么深刻的印象,只是有了一层模糊的脉络,刚好最近又接触了一些有关http的相关内 ...
- 搞懂Redis协议RESP
RESP (REdis Serialization Protocal) Redis客户端和服务端之间通信的协议.它很简单,建立在TCP协议上,提供简单.高性能.可读性强的数据序列化的规范和语义. 5种 ...
- 【http】http协议的队首阻塞
1 队首阻塞 就是需要排队,队首的事情没有处理完的时候,后面的人都要等着. 2 http1.0的队首阻塞 对于同一个tcp连接,所有的http1.0请求放入队列中,只有前一个请求的响应收到了,然后才能 ...
- http协议的队首阻塞
1 队首阻塞 就是需要排队,队首的事情没有处理完的时候,后面的人都要等着. 2 http1.0的队首阻塞 对于同一个tcp连接,所有的http1.0请求放入队列中,只有前一个请求的响应收到了,然后才能 ...
- 搞懂分布式技术4:ZAB协议概述与选主流程详解
搞懂分布式技术4:ZAB协议概述与选主流程详解 ZAB协议 ZAB(Zookeeper Atomic Broadcast)协议是专门为zookeeper实现分布式协调功能而设计.zookeeper主要 ...
- 搞懂分布式技术2:分布式一致性协议与Paxos,Raft算法
搞懂分布式技术2:分布式一致性协议与Paxos,Raft算法 2PC 由于BASE理论需要在一致性和可用性方面做出权衡,因此涌现了很多关于一致性的算法和协议.其中比较著名的有二阶提交协议(2 Phas ...
- [转帖]USB-C和Thunderbolt 3连接线你搞懂了吗?---没搞明白.
USB-C和Thunderbolt 3连接线你搞懂了吗? 2018年11月25日 07:30 6318 次阅读 稿源:威锋网 3 条评论 按照计算行业的风潮,USB Type-C 将会是下一代主流的接 ...
随机推荐
- Java I/O(3):NIO中的Buffer
您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来- 之前在调用Channel的代码中,使用了一个名叫ByteBuffer类,它是Buffer的子类.这个叫Buffer的类是专门用来解决高速设备与低 ...
- 前端开发日常——CSS动画无限轮播
近来没有什么值得写的东西,空闲的时候帮前端的同学做了些大屏上的展示模块,就放在这里写写吧,手把手"需求->设计-> 实现",受众偏新手向. 为了直观便于理解, 直接把结 ...
- Codeforces Round #829 (Div. 2)/CodeForces1754
CodeForces1754 注:所有代码均为场上所书 Technical Support 解析: 题目大意 给定一个只包含大写字母 \(\texttt{Q}\) 和 \(\texttt{A}\) 的 ...
- 云原生之旅 - 5)Kubernetes时代的包管理工具 Helm
前言 上一篇文章 [基础设施即代码 使用 Terraform 创建 Kubernetes] 教会了你如何在Cloud上面建Kubernetes资源,那么本篇来讲一下如何在Kubernetes上面部署应 ...
- PageRank原理分析
pagerank是将众多网页看成一个有向图,每个页面就是有向图中的节点.计算每个节点的出度和入度.如果一个网站被大量其他的网页引用,那么他就会有更高的pr分数. 原理 对于所有与节点i相连的节点,用他 ...
- python简单的tcp服务端
1 #!/usr/bin/python 2 # -*- coding: UTF-8 -*- 3 # 文件名:tcpserver.py 4 5 import socket 6 import time 7 ...
- JS逆向实战5--JWT TOKEN x_sign参数
什么是JWT JWT(JSON WEB TOKEN):JSON网络令牌,JWT是一个轻便的安全跨平台传输格式,定义了一个紧凑的自包含的方式在不同实体之间安全传输信息(JSON格式).它是在Web环境下 ...
- pyinstaller 打包exe相关
-w 只有窗口,没有console -p 加入路径 -F 生成一个exe文件 有虚拟环境时,需要先在cmd中进入虚拟环境,再执行打包程序 # 生成一个exe 无窗口 有icon Pyside2 pyi ...
- h5 websocket 断开重新连接
最近的项目中使用ws 长连接来接收和发送消息, 直接上代码 import * as SockJS from "sockjs-client"; import Stomp from & ...
- Spring Cloud Loadbalancer
Spring Cloud Loadbalancer---客户端负载均衡器 springcloud 2020.0.1 版本之后 删除了eureka中的ribbon,替代ribbon的是spring cl ...