前端大量并发请求的处理方案

682 阅读4分钟

我的博客原文

@author: 郭瑞峰

@createTime: 2023/09/26

@updateTime: 2023/09/26

哔哔啵啵的前言

不得不吐槽了这两天开发,开发三方系统的概览页面,概览页面最大的特点就是图表多,还有并发请求。当然,我那个页面也就要请求48个接口,也不。。。多??

卧槽,48个接口,后端呢?救我一下啊,后端还有200个bug(或者说是票)等着解决呢。

emmm,48个接口,玩我呢?

emmm,肯定不能一次性并发请求,不然要炸

但是,万一能行呢?

不出意料的崩了

第一步,我们宣称48个没有什么事儿,直接套用现成的接口并发请求。

Weixin Screenshot_20230925165353.png

一切都好,48个接口一个都没报错

第二次测试时候,三方服务器炸了 (有的时候要从服务器上面找原因好吧,这么多年都是这么卡,有没有认真工作,好不好)

Weixin Screenshot_20230926093907.png

没办法,只有老老实实地分流请求

解决方案

用我不太聪明的小脑瓜想了想,想到仨解决方案

修改 axios maxContentLength 属性

若是用 axios 库的话,可以修改maxContentLength属性值来限制并发请求,当超过上限时候就会进入等待(可能是等待队列,这个后续自己调研一下把)

import axios from 'axios'

const request = axios.create({
  maxContentLength: 10
})

export default request

但最后否定了,原因在于axios已经进行过二次封装,不能随便更改(即使这个改动对其他模块无任何影响)

分流使用 Promise.all

注意:用的是Promise.all不是Promise.any Promise.all是等所有数组resolve后执行then Promise.any是数组有一个resolve后执行then

先上代码吧,可以用浏览器开发工具运行一下

const promise = (index) => new Promise((resolve, reject) => {
  setTimeout(() => {
    if (Math.random() <= 0.5) {
      resolve(`Promise ${index} run success`)
    } else {
      reject(`Promise ${index} get error`)
    }
  }, Math.random() * 1000)
})

// 举个50并发的例子
const list = new Array(50).fill((index) => promise(index))

const running = (index) => {
  if (index >= list.length) return
  Promise.all([
    list[index](index),
    list[index + 1](index + 1),
    list[index + 2](index + 2),
    list[index + 3](index + 3),
    list[index + 4](index + 4)
  ])
    .then((valueList) => {
      console.log('Promise.all run successfully')
      console.log(valueList)
      console.log('\n')
    })
    .catch(error => {
      console.log('Promise.all get error')
      console.log(error)
      console.log('\n')
    })
    .finally(() => {
      running(index + 5)
    })
}

running(0)

优点: 方便简单,通过分段方式解决了高并发请求

缺点也很明显:

  1. 同一批请求内要是有一个请求异常,其他同批次的请求就不会有后续操作
  2. 后续二次开发,迭代开发时候需要重新理一遍Promise.allthen返回值的顺序

多个链式请求

链式请求的实质是等待上一个请求完成后执行下一个请求(这可能是我自创的定义,也许有其他名词形容,希望大佬们评论区教我做人)

还是老样子,先上代码

const promise = (index) => new Promise((resolve, reject) => {
  setTimeout(() => {
    if (Math.random() <= 0.5) {
      resolve(`Promise ${index} run success`)
    } else {
      reject(`Promise ${index} get error`)
    }
  }, Math.random() * 1000)
})
  .then(data => {
    console.log(data)
    return list
  })
  .catch(error => {
    console.log(error)
    return list
  })

const list = new Array(50).fill(index => promise(index))

const promise = (index) => new Promise((resolve, reject) => {
  setTimeout(() => {
    if (Math.random() <= 0.5) {
      resolve(`Promise ${index} run success`)
    } else {
      reject(`Promise ${index} get error`)
    }
  }, Math.random() * 1000)
})
  .then(data => {
    console.log(data)
    return list
  })
  .catch(error => {
    console.log(error)
    return list
  })

const list = new Array(50).fill(index => promise(index))

const running = () => {
  let count = 0
  const worker = () => {
    if (count >= list.length) return // 设置退出条件
    list[count](count++)
      .then(() => {
        worker()
      })
  }
  // 设置五个链
  worker()
  worker()
  worker()
  worker()
  worker()
}

running()

在每个Promise中的thencatchreturn并发请求对象,这样当前接口执行完成后就可以继续执行then,然后再then中调用对应接口(这边建议使用对象方式调用,方便阅读和迭代开发)

优点:

  1. 每个请求以及后续处理相互独立,互不干扰
  2. 通过设置多个链式来削减并发请求数量
  3. 方便溯源接口

缺点很明显:

  1. 每个接口都要写后续操作以及异常处理,最后都要返回接口列表/对象
  2. 每个链式都要设置好(规划好)请求链路
  3. 会出现单链忙碌,其他链围观(接口简单,老早就摸鱼了)

最终

考虑再三,咱最终使用链式请求,很意外吧,居然不用Promise.all的方法

那么说一下为啥咱用链式请求,在于这是概览页面,每个接口后续处理必须相互独立。

若是有啥好的建议欢迎大佬们在评论区说一下

109951567_0_final.png