关于请求并发控制的思考

您所在的位置:网站首页 js如何尽快并发500个请求 关于请求并发控制的思考

关于请求并发控制的思考

2023-07-28 20:33| 来源: 网络整理| 查看: 265

前言

我们需要知道,在 http1.1 的版本中 Chrome 浏览器对于同域名下的并发请求限制在 6 个,并且这样的限制不能修改,而是在源码里写死的。那么问题来了,如果同时发起多个请求,浏览器会如何处理呢?另外,为什么 http1.1 会有这样的情况? http2.0 是不是也有同样的问题呢?

其实,在 http 的不同版本过程中,一直在优化接口的传输可靠性和速度的及时性。 http 是建立在 tcp 之上的协议,我们没有很好的办法优化 tcp,只能想办法优化自己。这里不过多说这个,下面的思考都基于 http1.1 的基础上,http 2.0 有多路复用,不会有 6 个 tcp 链接的情况,是复用一个 tcp 连接。

本文的目录结构如下:

Chrome浏览器对于多个请求的处理情况 简单的 http 控制 关于 fetch 的实现 关于 promise 的实现 写在最后 Chrome浏览器对于多个请求的处理情况

在这里,我们模拟了 17 个接口几乎同时请求的情况,如下图:

上图可以发现,浏览器会几乎同时发起这些请求,全部处于 pending 状态。

但是从上面这张图我们又可以发现,接口真的开始请求响应之后,第六个接口之后的请求会被阻塞。直到前面的请求被完成的时候,才会依次执行后续的接口。前面有很长一段的等待时间。

这似乎是一个非常好的解决大量接口请求,可能导致的 tcp 链接过多或者服务器扛不住的情况。

但是这里也会遇到一个问题: 如果实际的接口响应时间很慢,同时请求大量接口,对于处于后面pending的接口而言,有没有超时的风险呢?

其实肯定是有的,那么我们还有什么可以优化的方法吗?

我的想法是,既然浏览器可以控制接口的阻塞,使其一直等待。那我们也可以控制接口的请求数量,我们在最大限制量内,分批次处理请求,请求结束后,再去请求后续的接口。

我们从下面的几个方案来大概讲解一下,遇见这种情况,前端如何更好的处理。

我们先创建一个含有多请求 url 的数组,和最大并发量的限制变量,作为后面代码的前提条件。

const pics = [ 'https://****/mock/**/getListForOpeCallPage', 'https://****/mock/**/listPage', 'https://****/mock/**/getCsCallList', 'https://****/mock/**/getPhoneLog', 'https://****/mock/**/getPhoneResult', 'https://****/mock/**/listInboundInstanceById', 'https://****/mock/**/inboundInstanceInfo', 'https://****/mock/**/getHidePhone', 'https://****/mock/**/listAllStaff', 'https://****/mock/**/selectBindCompanyListByRobotDefId', 'https://****/mock/**/getResultListByCall', 'https://****/mock/**/updateResult', 'https://****/mock/**/getInboundInstanceResult', 'https://****/mock/**/listInboundInstanceLog', 'https://****/mock/**/getResultList', 'https://****/mock/**/virtual', 'https://****/mock/**/actual', ]; const maxLoad = 5; 复制代码 简单的 http 控制

http 的请求限制很简单,做到以下三个点即可:

我们需要一个 maxLoad 变量,控制实时发起请求的数量。这里临时定为 5。 还需要一个临时的下标,用于当某个接口完成的时候处理哪一个未请求的接口。 在 http 的 onload 回调里,递归调用请求函数。

代码如下:

function getUrlByHttp() { let idx = maxLoad; function getContention(index) { console.log('我在执行', index); const conn = new XMLHttpRequest(); conn.open('get', pics[index]); conn.onload = () => { console.log('当前是哪个id先返回', index); idx++; if(idx < pics.length){ getContention(idx); } }; conn.send(); } function start() { for (let i = 0; i < maxLoad; i++) { getContention(i); } } start(); } 复制代码

我们一起看一下结果,如下图:当点击按钮的时候,会同时发起 5 个请求。并不会把所有的接口全部都发出。而这5个限制是由变量控制的,我们可以按情况更改最大并发的量。

那么当请求到后期,我们可以看见下图的加载顺序,在前面的 5 个接口请求中,任意一个接口结束后,就会马上发起下一个请求。在任意的时间段,请求数量都一定小于等于最大并发量:5。

关于上面的代码,我们还可以升级一下代码,使用更简便的 api 来实现。

关于 fetch 控制

虽然是http方法的升级版本,但其实和http一样的方法,只是减少了代码量,更干净而已。

function getUrlByFetch() { let idx = maxLoad; function getContention(index) { fetch(pics[index]).then(() => { idx++; if(idx < pics.length){ getContention(idx); } }); } function start() { for (let i = 0; i < maxLoad; i++) { getContention(i); } } start(); } 复制代码

这里可以注意,下一次执行不是绑定在 onload 的方法上,而是在有返回值的时候。

接口的最后完成时间会更短,因为不用等待资源下载的时间,如下图所示:

fetch 方法,其实也并不能完全满足目前项目的需求和编码习惯,我们可以尝试更高级的用法 - promise。

关于 promise 的实现

这里实现的功能和上面的需求不太一样,这里的需求是:现有大量的接口,使用 pomise 执行这些接口,保证每个时间的最大请求数在 5 个,并在接口全部成功或者失败的时候返回结果。

这个需求有 3 个重点:

在接口全部成功或者失败的时候,告知成功或者失败。

此时,我们肯定会首先想到使用 promise.all,这个api会返回请求数组全部请求成功或者失败的结果。

但是我们如何对请求的数组进行最大并发量的限制呢?

如果最大限制为 5,那么 promise.all 的数组长度只能是 5。

在长度被限制的情况下,我们如果保真所有的接口被一个接一个请求呢?

我们可以使用 Promise.race 监控当前 Promise.all 的接口数组的请求情况,返回第一个请求成功的接口。

具体代码如下:

function getUrlByPromise() { /** * 一个执行异步逻辑的promise函数,返回成功的异步id,或者失败的id */ function getFetch(url, idx) { return new Promise(async (resolve, reject) => { console.log(`发起第${idx}个请求`); const res = await fetch(url); if (!res) { return reject(); } return resolve(); }); } function limitLoad() { // 限制请求数量的数组,idx是第几个位置,用于验证是第几个位置的接口请求成功,需要更换接口 const promises = pics.slice(0, maxLoad).map((it, idx) => { // 这里返回结束的idx,是限制数组的下标 return getFetch(it, idx).then(() => idx); }); // 这里的reduce返回一个Promise.resolve()的promise,是包含了里面所有的feach请求回调注册完成的 return ( pics .reduce((pre, cur, index) => { if (index < maxLoad) { return pre; } return ( pre // 这里的对未来的请求的注册,先给每一个item注册这样的函数 // 当回调被执行的时候,就是某个位置的请求完成,并且返回位置的下标 .then(() => Promise.race(promises)) .catch((err) => console.log(err)) .then((idx) => { // 第几个位置的请求结束,就重新放入一个请求,这个请求是当前的下标,并且返回当前的位置 console.log( `第${idx}个位置的请求结束,将第${index}个接口放入,共${pics.length}个请求` ); promises[idx] = getFetch(cur, index).then(() => idx); }) ); }, Promise.resolve()) // promise.all控制并发数 .then(() => Promise.all(promises)) ); } // 开始函数 function start() { limitLoad() .then((res) => { console.log('资源全部加载成功', res); }) .catch((rej) => { console.log('资源加载失败', rej); }); } start(); } 复制代码

我们一起来看一下结果:也是非常完美的串行+并行执行的效果。

我们再来看看响应的log输出,注意每一个log的输出顺序,第几个被执行,第几个请求结束,所有请求执行结束。

关于使用promise的控制并发量的方法,网上已经有很多现成的demo和库。这里demo的实现是完全自己的思路去完成的,大家可以多多参考,找一个自己最能理解的方式去实现。

写在最后

到这里,本文就结束了。最后,给出两个问题让大家来思考一下:

1.浏览器为什么要对接口的请求量做限制?

2.为什么要使用reduce,reduce处理异步的问题有什么优势吗?

如果有想法,可以直接在评论区回复哦。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3