优化实战 第 03 期 - 对异步任务并发量进行限流

浏览器同域名下对并发请求的数量是有限制的,通常是 4 ~ 8 以内。超出的会被置入队列等待发送,即 待处理 pending 状态

如果并发请求量达到一定量级的时候,堆积了无数的调用栈就有可能会导致 内存溢出

浏览器的并发限制

  • Chrome 浏览器

    同一域名同时最多只能建立 6TCP 连接,也就是说单个域名最大的并发量不超过 6

  • Safari 浏览器

    同一域名同时最多只能建立 4TCP 连接,也就是说单个域名最大的并发量不超过 4

协议的并发限制(HTTP1.1)

  • 持续连接

    支持 TCP 连接的复用,持续连接在默认情况下是激活的,可以被多个请求复用

    且不需要声明 Connection: keep-alive

  • 管线化

    HTTP 请求每次只能是请求一次响应一次的模式进行改进

    允许在第一个响应被完全发送之前就可以发送第二个请求(可以一次性发送多个请求),以降低通信延迟

  • 队头堵塞

    在接收响应报文时,必须依赖请求顺序接收。如果前一个请求遇到了阻塞,后面的请求即使已经处理完了,仍然需要等待阻塞的请求处理完成

    pipeline.jpeg

场景示例

  • 业务场景

    假设现在有 1000 个异步任务需要执行,但出于性能的考虑,我们必须将执行的数量控制在 3 个以内,同时还要尽可能快的拿到响应结果

    如:大文件的批量上传、大量的图片加载等 量化异步任务的执行且影响性能 的场景

  • 生活示例

    ticket.png 排队购票和异步任务并发的场景类似,只有三台售票机,当购票人数超过三个后,就需要依次进行排队等候

优化方案

  • 参数注解

    /**
    * @description 异步任务并发量的控制
    * @param {Array} list 迭代数组
    * @param {Number} limit 控制的并发数量
    * @param {Function} handler 对list每一项的处理函数
    */
    复制代码
  • 首先,通过 while 循环实现初始并发

    while(limit--) {
      handler(list.shift())
    }
    复制代码
  • 然后,通过递归依次执行下一个,直到全部执行完

    const runInSequence = async (list, handler, callback) => {
      const item = list.shift()
      if (item) {
        const result = await handler(item)
        callback(result)
        list.length && runInSequence(list, handler, callback)
      }
    }
    复制代码
  • 最后,组合以上逻辑

    const asyncThrottling = ({ list, limit = 3, handler = () => {} }) => {
      const response = [], len = list.length
      return new Promise(resolve => {
        limit = len > limit ? limit : len
        while(limit--) {
          runInSequence(list, handler, result => {
            response.push(result)
            response.length === len && resolve(response)
          })
        }
      })
    }
    复制代码
  • 一起交流学习

    加群交流看沸点