怎么去设计一个任务队列?控制请求的并发数量

389 阅读2分钟

需求背景

今天去完成一个列表的请求的时候,发现因为自己一开始发送的http请求过多的时候,后面的请求直接报错的,这是因为啥呢?

才知道后台的话,限制了在同一时间处理请求的数量,种情况下,后端改一下也可以。而且浏览器也会有对不是http2请求有限制,有的浏览器在同一时间内规定了不能大于6条请求。后端的咱们不去思考,为啥浏览器会有并发的限制呢

  1. 对操作系统端口资源考虑

PC总端口数为65536,那么一个TCP(http也是tcp)链接就占用一个端口。操作系统通常会对总端口一半开放对外请求,以防端口数量不被迅速消耗殆尽。

  1. 过多并发导致频繁切换产生性能问题

一个线程对应处理一个http请求,那么如果并发数量巨大的话会导致线程频繁切换。而线程的上下文切换有时候并不是轻量级的资源。这导致得不偿失,所以请求控制器里面会产生一个链接池,以复用之前的链接。所以我们可以看作同域名下链接池最大为4~8个,如果链接池全部被使用会阻塞后面请求任务,等待有空闲链接时执行后续任务。

  1. 避免同一客户端并发大量请求超过服务端的并发阈值

在服务端通常都对同一个客户端来源设置并发阀值避免恶意攻击,如果浏览器不对同一域名做并发限制可能会导致超过服务端的并发阀值被BAN掉。

  1. 客户端良知机制

为了防止两个应用抢占资源时候导致强势一方无限制的获取资源导致弱势一方永远阻塞状态。

如何解决

我们假设在同一时间会有20条请求发送,然后我们创建一个队列,设置max,让它在同一时间只能发送max个请求(setTimeout模拟发送请求)

function createTask(i) {
  return () => {
    return new Promise(res => {
      setTimeout(() => {
        res(i)
      }, 2000)
    })
  }
}

class TaskQueue {
  constructor() {
    this.max = 10
    this.taskList = []
    setTimeout(() => {
      this.run()
    }, 0)
  }
  addTask(task) {
    this.taskList.push(task)
  }
  run() {
    let len = this.taskList.length
    if(!len) return
    let min = Math.min(this.max, len)
    for(let i = 0; i < min; i++) {
      let task = this.taskList.shift()
      this.max--
      task().then((res) => {
        console.log(res)
      }).catch(e => {
        console.log(e)
      }).finally(() => {
        this.max++
        this.run()
      })
    }
  }
}

const taskQueue = new TaskQueue()
for(let i = 0; i < 20; i++) {
  const task = createTask(i)
  taskQueue.addTask(task)
}