- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
这一篇文章,我们核心要聊的事情就是HTTP的对头阻塞问题,因为HTTP的核心改进其实就是在解决HTTP的队头阻塞。所以,我们会讲的理论多一些,而实践其实很少,要学习的头字段也只有一个,我会在最开始就讲完这个头字段,然后我们安心的去学习接下来的理论知识,嗯……这些理论知识很重要.
那我们就先来看看我们本篇要学的这个唯一的头字段是什么吧.
其实在聊这个字段之前,我们要先学一些前置知识才好,但是我想先讲这个字段,后面再带着疑问去学理论,嗯……就这样.
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/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目前为止最完美的解决方案了。但是故事并没有就此停止.
虽然,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协议07之队头阻塞真的很烦人的文章就讲到这里了,如果你想了解更多关于真正“搞”懂HTTP协议07之队头阻塞真的很烦人的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在尝试在两个表之间进行空间连接: 表 1:397265 个特征(在 geom 字段上有 gist 索引) 表 2:73 个特征(在 geom 字段上有 gist 索引) 表 1 和表 2 具有相同
我正在尝试在两个表之间进行空间连接: 表 1:397265 个特征(在 geom 字段上有 gist 索引) 表 2:73 个特征(在 geom 字段上有 gist 索引) 表 1 和表 2 具有相同
枚举类型的值是该类型的静态变量。 据我所知,变量是由引用变量引用的,但没有新的运算符来实例化枚举对象。但这就像初始化数组一样吗? 这是对还是错? 最佳答案 是的,枚举类型的文字是 public sta
我阅读了有关关闭 zsh 自动更正以完成命令的所有提示。但是,它们并没有完全发挥作用。我试过 DISABLE_CORRECTION="true", unsetopt correct, unsetopt
我知道这个问题是 answered before ,但给出的答案并不是完整的故事: 我进入了 Firefox 的 Options->Content 并删除了除德语/德国之外的所有语言,navigato
我知道用汇编语言编写任何内容或将汇编语言添加到任何程序都会损害其可移植性。但是,有多糟糕呢?我的意思是,现在基本上所有 PC 都是 x86 或 x64,对吧?那么,如果我将汇编嵌入到 C 程序中,为什
我正计划构建一个 Web 服务客户端,它始终检查数据库中的某些记录,并根据数据库内容的结果在每个时刻及时执行某些决策。 所以我在想,我怎样才能让客户端一直运行呢? 我唯一想到的就是无限循环。像这样的东
我无法获取小部件的实际背景颜色。在我的特殊情况下,我在使用 QTabWidget 中的小部件时遇到了问题。 这是在 Windows7 上。所以经典小部件有一些灰色背景,而选项卡中的小部件通常用白色背景
请不要将我指向How to wrap preference title?因为它不适用于(正如我评论的那样)您使用 @strings/ 的情况对 strings.xml 文件的引用。 如果你使用 and
情况如下: 已知hdfs是仅附加的(本身没有更新)。 配置单元将数据写入其位于hdfs中的仓库。 可以在配置单元中执行更新 这意味着写入了新数据,旧数据应该以某种方式标记为已弃用,然后在某个时间将其清
在javascript中删除cookies的方法是将过期日期设置为过去。现在这实际上并没有删除 cookie,至少在 Firefox 中是这样。这只是意味着 cookie 将在浏览器关闭时被删除。 这
我需要终止一个卡住的线程,我将 IsBackground 设置为 true 但它仍然存在。线程的属性: ThreadState = AbortRequested IsBackground = true
在逻辑中,以及在 *ahem* 正确设计的编程语言中,将 boolean 值与 true 进行比较总是多余的,即 a == True 应该简单地替换为 a 。 (类似地, a == False 由 n
我一直在努力寻找一个好的定义,并理解线程到底是什么。 看来我一定错过了一些明显的东西,但是每次我读到什么是线程时,它几乎是一个循环定义,la“线程是一个执行线程”或“一种划分运行任务的方法” ”。呃呃
就目前而言,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引起辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the he
是否可以在 MAC 上以真正的全屏模式运行 IntelliJ Idea? 没有工具栏、侧边栏、按钮,只有代码。 如果可以,请告诉我。 最佳答案 您可以通过禁用以下项目在 View 菜单中执行此操作:
考虑以下代码: case class Vector3(var x: Float, var y: Float, var z: Float) { def add(v: Vector3): Unit =
我试图确认这个说法是否属实: 模型包括: 持久层:本质上是 DAO + 表示表的类 + DTO 服务层:DAOS + 一些逻辑的组合 您能否也引用/支持您的回答?我相信我在Spring Framewo
给定代码: #include struct X {}; struct Y1: virtual X {}; struct Y2: virtual X {}; struct Y3: virtual X
从这个其他QUESTION他们谈论 Bjarne Stroustrup 是如何说的,就像比 int 窄的整数数据类型(例如 short)被提升为 int,float 被提升为 double。但是,与i
我是一名优秀的程序员,十分优秀!