异步为什么会造成 HTTP 队首阻塞?

一、http 协议的队首阻塞

队首阻塞,队首的事情没有处理完的时候,后面的都要等着。

1.1 HTTP1.0 的队首阻塞

对于同一个 tcp 连接,所有的 http1.0 请求放入队列中,只有前一个请求的响应收到了,然后才能发送下一个请求。http1.0 的队首组塞发生在客户端。

1.2 HTTP1.1 的队首阻塞

HTTP1.1 版本上使用了一种 Pipelining 管道技术来并行发送和处理多个请求。让客户端能够并行发送多个请求,服务器端也可以并行处理多个来自客户端的请求。在一个 TCP 连接中,发送多个 HTTP 请求,不需要等待服务器端对前一个请求的响应之后,再发送下一个请求。但是使用了管道技术的 HTTP/1.1,根据 HTTP/1.1 的规则,服务器端在响应时,要严格按照接收请求的顺序发送,即先接收到的请求,需要先发送其响应,客户端浏览器也是如此,接收响应的顺序要按照自己发送请求的顺序来。这样造成的问题是,如果 最先收到的请求的处理时间长的话,响应生成也慢,就会阻塞已经生成了的响应的发送。也会造成队首阻塞。

总的来说,管道技术允许客户端和服务器端并行发送多个请求和响应,但是客户端接收响应的顺序要和自己发送请求的顺序对应,服务器端发送响应的顺序要和自己接收到的请求的顺序对应,这样做似乎没什么问题,看起来是不是“FIFO”先来先服务的方式,如果前面收到的一个请求,在服务器端处理的时间很长,生成响应需要很多时间,那么对于后面的已经处理完生成响应的请求来说,它们只能阻塞等待,等待前面的响应发送完后,自己才能被发送出去(即使该请求的响应已经生成),造成了“队首阻塞”问题。可见队首阻塞发生在服务器端,虽然服务器端并行接收了多个请求,也并行处理生成多个响应,但由于要遵守 HTTP/1.1 的规则,先接收到的请求需要先发送响应,造成了阻塞问题。

另外需要注意的是,虽然 HTTP/1.1 规范中规定了 Pipelining 管道技术来并行发送和处理多个请求,但是这个功能在浏览器中默认是关闭的。

看一下 Pipelining 是什么,RFC 2616 中的规定 A client that supports persistent connections MAY “pipeline” its requests (i.e., send multiple requests without waiting for each response). A server MUST send its responses to those requests in the same order that the requests were received. 一个支持持久连接的客户端可以在一个连接中发送多个请求(不需要等待任意请求的响应)。收到请求的服务器必须按照请求收到的顺序发送响应。 至于标准为什么这么设定,我们可以大概推测一个原因:由于 HTTP/1.1 是个文本协议,同时返回的内容也并不能区分对应于哪个发送的请求,所以顺序必须维持一致。比如你向服务器发送了两个请求 GET /query?q=A 和 GET /query?q=B,服务器返回了两个结果,浏览器是没有办法根据响应结果来判断响应对应于哪一个请求的。

Pipelining 这种设想看起来比较美好,但是在实践中会出现许多问题:

  • 一些代理服务器不能正确的处理 HTTP Pipelining。
  • 正确的流水线实现是复杂的
  • Head-of-line Blocking 连接头阻塞:在建立起一个 TCP 连接之后,假设客户端在这个连接连续向服务器发送了几个请求。按照标准,服务器应该按照收到请求的顺序返回结果,假设服务器在处理首个请求时花费了大量时间,那么后面所有的请求都需要等着首个请求结束才能响应。

所以现代浏览器默认是不开启 HTTP Pipelining 的。

1.3 HTTP2 队首阻塞

对于 HTTP1.1 中管道化导致的请求/响应级别的队头阻塞,可以使用 HTTP2 解决。HTTP2 不使用管道化的方式,而是引入了帧、消息和数据流等概念,每个请求/响应被称为消息,每个消息都被拆分成若干个帧进行传输,每个帧都分配一个序号。每个帧在传输是属于一个数据流,而一个连接上可以存在多个流,各个帧在流和连接上独立传输,到达之后在组装成消息,这样就避免了请求/响应阻塞。

当然,即使使用 HTTP2,如果 HTTP2 底层使用的是 TCP 协议,仍可能出现 TCP 队头阻塞。因为 HTTP/2 并没有解决 TCP 的队首阻塞问题,它仅仅是通过多路复用解决了以前 HTTP1.1 管线化请求时的队首阻塞。比如 HTTP/1.1 时代建立一个 TCP 连接,三个请求组成一个队列发出去,服务器接收到这个队列之后会依次响应,一旦前面的请求阻塞,后面的请求就会无法响应。HTTP/2 是通过分帧并且给每个帧打上流的 ID 去避免依次响应的问题,对方接收到帧之后根据 ID 拼接出流,这样就可以做到乱序响应从而避免请求时的队首阻塞问题。但是 TCP 层面的队首阻塞是 HTTP/2 无法解决的(HTTP 只是应用层协议,TCP 是传输层协议),TCP 的阻塞问题是因为传输阶段可能会丢包,一旦丢包就会等待重新发包,阻塞后续传输,这个问题虽然有滑动窗口(Sliding Window)这个方案,但是只能增强抗干扰,并没有彻底解决。

二、TCP 队首阻塞

队首阻塞(head-of-line blocking)发生在一个 TCP 分节丢失,导致其后续分节不按序到达接收端的时候。该后续分节将被接收端一直保持直到丢失的第一个分节被发送端重传并到达接收端为止。该后续分节的延迟递送确保接收应用进程能够按照发送端的发送顺序接收数据。这种为了达到完全有序而引入的延迟机制非常有用,但也有不利之处。

假设在单个 TCP 连接上发送语义独立的消息,比如说服务器可能发送 3 幅不同的图像供 Web 浏览器显示。为了营造这几幅图像在用户屏幕上并行显示的效果,服务器先发送第一幅图像的一个断片,再发送第二幅图像的一个断片,然后再发送第三幅图像的一个断片;服务器重复这个过程,直到这 3 幅图像全部成功地发送到浏览器为止。

要是第一幅图像的某个断片内容的 TCP 分节丢失了,客户端将保持已到达的不按序的所有数据,直到丢失的分节重传成功。这样不仅延缓了第一幅图像数据的递送,也延缓了第二幅和第三幅图像数据的递送。

2.1 如何解决 TCP 队头阻塞

TCP 中的队头阻塞的产生是由 TCP 自身的实现机制决定的,无法避免。想要在应用程序当中避免 TCP 队头阻塞带来的影响,只有舍弃 TCP 协议。

比如 google 推出的 quic 协议,在某种程度上可以说避免了 TCP 中的队头阻塞,因为它根本不使用 TCP 协议,而是在 UDP 协议的基础上实现了可靠传输。而 UDP 是面向数据报的协议,数据报之间不会有阻塞约束。

此外还有一个 SCTP(流控制传输协议),它是和 TCP、UDP 在同一层次的传输协议。SCTP 的多流特性也可以尽可能的避免队头阻塞的情况。

三、总结

从 TCP 队头阻塞和 HTTP 队头阻塞的原因我们可以看到,出现队头阻塞的原因有两个:

  • 独立的消息数据都在一个链路上传输,也就是有一个“队列”。比如 TCP 只有一个流,多个 HTTP 请求共用一个 TCP 连接
  • 队列上传输的数据有严格的顺序约束。比如 TCP 要求数据严格按照序号顺序,HTTP 管道化要求响应严格按照请求顺序返回

所以要避免队头阻塞,就需要从以上两个方面出发,比如 quic 协议不使用 TCP 协议而是使用 UDP 协议,SCTP 协议支持一个连接上存在多个数据流等等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/114857.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++之前置声明

在C中,前置声明是一种声明类或函数的方式,但并不定义它们。 前置声明的主要目的是为了解决编译时的依赖性问题,提高编译效率,并允许更灵活的代码组织。 原理 C前置声明可以减少头文件依赖的原理在于,通过前置声明&am…

Qt扫盲- QTextStream 理论总结

QTextStream 理论总结 一、概述二、使用1. 构造函数2. 立即写入3. 编码4. 读取 三、格式化 一、概述 QTextStream类为读写文本提供了一个方便的接口。QTextStream可以操作QIODevice、QByteArray或QString。使用QTextStream的流操作符,我们可以方便地读写单词、行和…

LABVIEW 安装教程(超详细)

目录 LabVIEW2017(32/64位)下载地址: 一 .简介 二.安装步骤: LabVIEW2017(32/64位)下载地址: 链接: https://pan.baidu.com/s/1eSGB_3ygLNeWpnmGAoSwcQ 密码:gjrk …

JAVA面经整理(MYSQL篇)

索引: 索引是帮助MYSQL高效获取数据的排好序的数据结构 1)假设现在进行查询数据,select * from user where userID89 2)没有索引是一行一行从MYSQL进行查询的,还有就是数据的记录都是存储在MYSQL磁盘上面的,比如说插入数据的时候是向磁盘上面…

C++ 类和对象(六)赋值运算符重载

1 运算符重载 C为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数, 也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。 函数名字为:关键字operator后面接需…

css之Flex弹性布局(父项常见属性)

文章目录 🐕前言:🏨定义flex容器 display:flex🏨在flex容器中子组件进行排列🪂行排列 flex-direction: row🪂将行排列进行翻转排列 flex-direction: row-reverse🏅按列排列 flex-direction: col…

No170.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

mstsc改端口为33389

windows 远程默认端口3389不太安全,改成33389防下小人 把下面的2个文本存在后缀.reg的文件,双击导入注册表,"PortNumber"dword:0000826d 这个就是33389对应的端口号的16进制值,要想自己改成其它的换下值即可 Windows …

人工智能、机器学习、深度学习的区别

人工智能涵盖范围最广,它包含了机器学习;而机器学习是人工智能的重要研究内容,它又包含了深度学习。 人工智能(AI) 人工智能是一门以计算机科学为基础,融合了数学、神经学、心理学、控制学等多个科目的交…

LeetCode讲解篇之77. 组合

文章目录 题目描述题解思路题解代码 题目描述 题解思路 遍历nums,让当前数字添加到结果前缀中,递归调用,直到前缀的长度为k,然后将前缀添加到结果集 题解代码 func combine(n int, k int) [][]int {var nums make([]int, n)fo…

分享转发API

分享转发API 微信小程序可以在page中定义 onShareAppMessage函数,设置该页面的分享信息。只有定义了此事件处理函数,小程序右上角菜单栏才会显示“转发”按钮,用户点击页面内的转发按钮是才能调用函数, 除了在这里点击转发按钮进行…

最新!两步 永久禁止谷歌浏览器 Google Chrome 自动更新

先放效果图: CSDN这个问题最火的大哥的用了没用 像他这样连浏览器都打不开 为什么要禁止chrome自动更新 看到很多搞笑的大哥,说为啥要禁止; 我觉得最大的原因就是chromedriver跟不上chrome的自动更新,导致我们做selenium爬虫的…

MySQL数据库查询实战操作

前置条件: 创建库:MySQL基本操作之创建数据库-CSDN博客 创建表:MySQL基本操作之创建数据表-CSDN博客 目录 常规查询常用函数union查询一、常规查询 普通的查询方式 1、查询所有姓名以 "张" 开头的学生: SELECT * FROM student WHERE name LIKE 张%; 这条语…

Golang爬虫入门指南

引言 网络爬虫是一种自动化程序,用于从互联网上收集信息。随着互联网的迅速发展,爬虫技术在各行各业中越来越受欢迎。Golang作为一种高效、并发性好的编程语言,也逐渐成为爬虫开发的首选语言。本文将介绍使用Golang编写爬虫的基础知识和技巧…

支付风控规则

支付宝使用基本风控规则 一、 6个规则 1、规则一:30分钟内,不要连续刷3笔(包括失败交易),两笔交易时间间隔大于5分钟,交易金额不要一样,不要贴近限额; 2、规则二:非正…

ETL工具对比

ETL开发 ETL是英文Extract-Transform-Load的缩写,表示将数据从来源端,经过抽取,转换,加载到目标数据源的过程。 数据抽取 分为全量抽取和增量抽取,数据量达到百万级别建议用增量抽取,小于百万级别可用增…

matlab中绘制 维诺图(Voronoi Diagram)

1.专业术语(相关概念): 基点Site:具有一些几何意义的点 细胞Cell:这个Cell中的任何一个点到Cell中基点中的距离都是最近的,离其他Site比离内部Site的距离都要远。 Cell的划分:基点Site与其它的…

Java中的static关键字

一、static关键字的用途 在《Java编程思想》P86页有这样一段话: “static方法就是没有this的方法。在static方法内部不能调用非静态方法,反过来是可以的。而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。这实际上正是s…

frida中使用gson打印map对象

Java.openClassFile("/data/local/tmp/r0gson.dex").load();const gson Java.use(com.r0ysue.gson.Gson);var Gson Java.use(‘com.google.gson.Gson’).$new(); console.log("map -> " Gson.toJsonTree(map).getAsJsonObject());

JS小数运算出现00000多位小数怎么解决

JS小数运算出现00000多位小数怎么解决 给大家分享一个经典的前端面试题: 0.10.2 0.3 //false javascript中浮点数的计算是以2进制计算的,所以0.10.2变成:0.30000000000000004 解决方案一 首先最简单的是利用JavaScript 的toFixed(n) 方…