前端发起同时发起1000个接口请求,甚至更多

202 阅读4分钟

为了实现前端批量处理1000个接口请求,并按照每10个接口请求分一组的方式进行,同时处理可能出现的失败请求并进行重试,我们可以采用如下步骤来设计这个过程:

1. 设计数据结构

  • 请求队列:用于存储待处理的所有接口请求。
  • 错误队列:用于存储执行失败的接口请求及其尝试次数。

2. 实现逻辑

  • 初始化请求队列:将1000个接口请求加入到请求队列中。
  • 按批次处理请求:从请求队列中取出10个请求进行处理,直到请求队列为空。
  • 错误处理与重试:对于失败的请求,将其加入错误队列,并记录尝试次数。当所有请求都处理完后,从错误队列中取出失败的请求进行重试,最多重试3次。

遇到连续多次失败的情况,一个好的做法是实施一种称为“指数退避”(Exponential Backoff)的策略。这种策略可以帮助避免在网络不稳定或服务暂时不可用的情况下频繁地重试请求,从而减轻服务器的压力,并给服务器恢复的时间。

下面是针对连续多次失败情况的改进版 BatchRequestHandler 类,其中包括了指数退避重试机制:

class RequestQueue {
  #queue = []; // 主请求队列
  #failedRequests = []; // 失败请求队列
  #retries = {}; // 记录每个URL的重试次数
  #groupSize = 10; // 每组的大小
  #maxRetries = 3; // 最大重试次数
  #baseDelay = 1000; // 初始延迟时间(毫秒)

  constructor(groupSize = 10, maxRetries = 3, baseDelay = 1000) {
    this.#groupSize = groupSize;
    this.#maxRetries = maxRetries;
    this.#baseDelay = baseDelay;
  }

  #enqueue(endpoint) {
    this.#queue.push(endpoint);
  }

  async #processQueue() {
    while (this.#queue.length > 0) {
      const group = this.#queue.splice(0, this.#groupSize); // 获取队列中的前10个元素
      try {
        const responses = await Promise.all(group.map(async (endpoint) => {
          // const response = await fetch(endpoint);
          // if (!response.ok) {
          //   throw new Error(`HTTP error! Status: ${response.status}`);
          // }
          // return response.json();
          console.log(endpoint,'api-url');
        }));

        // 处理响应数据
        console.log(responses,'res');
      } catch (error) {
        console.error('Error fetching data:', error);
        this.#failedRequests.push(...group); // 将失败的请求加入到失败请求队列中
      }
    }

    // 如果有失败的请求,重新请求它们
    if (this.#failedRequests.length > 0) {
      console.log('Retrying failed requests...');
      await this.#retryFailedRequests();
    }
  }

  async #retryFailedRequests() {
    let retriesLeft = true;

    while (retriesLeft && this.#failedRequests.length > 0) {
      retriesLeft = false; // 默认为false,如果有请求未达到最大重试次数,则设为true

      const group = this.#failedRequests.splice(0, this.#groupSize); // 获取失败请求队列中的前10个元素
      try {
        const responses = await Promise.all(group.map(async (endpoint) => {
          const retryCount = this.#retries[endpoint] || 0; // 获取当前URL的重试次数
          if (retryCount < this.#maxRetries) {
            retriesLeft = true; // 表示还有请求未达到最大重试次数
            this.#retries[endpoint] = retryCount + 1; // 更新重试次数

            // 应用指数退避策略
            const delay = this.#baseDelay * Math.pow(2, retryCount);
            await new Promise(resolve => setTimeout(resolve, delay));
          } else {
            console.warn(`Maximum retries reached for ${endpoint}`);
          }

          // const response = await fetch(endpoint);
          // if (!response.ok) {
          //   throw new Error(`HTTP error! Status: ${response.status}`);
          // }
          // return response.json();
        }));

        // 处理响应数据
        console.log(responses);
      } catch (error) {
        console.error('Error fetching data:', error);
        this.#failedRequests.push(...group); // 如果再次失败,重新加入到失败请求队列中
      }
    }
  }

  startProcessing (apiUrls) {
    // 将所有的 URL 加入队列
    this.#enqueue(apiUrls)
    this.#processQueue();
  }
}

// 假设你有一个包含 1000 个 API URL 的数组
const apiUrls = Array.from({length: 1000}, (_, i) => `https://api.example.com/data/${i + 1}`);

// 创建一个请求队列实例
const requestQueue = new RequestQueue(10, 3, 1000); // 设置每组10个请求,最多重试3次,初始延迟1秒

// 开始处理队列
requestQueue.startProcessing(apiUrls);

解释:

  1. 初始化:

    • requestQueue:存储待处理的请求。
    • errorQueue:存储失败的请求及其重试次数。
  2. 处理请求 (processRequests 方法):

    • 这个方法是处理请求的主要入口点。
    • 当主队列或错误队列中还有请求时,循环处理请求。
  3. 处理批次 (processNextBatch 方法):

    • 从主队列中取出一个批次的请求。
    • 使用 Promise.all 并行处理这批请求。
    • 对于每个请求,尝试调用 apiCall 并捕获任何可能发生的错误。
  4. 重试错误 (retryErrors 方法):

    • 如果错误队列不为空,则遍历错误队列中的请求。
    • 对于每个未成功的请求,检查是否达到了最大重试次数。
    • 如果没有达到最大重试次数,则重新调用 apiCall
    • 如果成功,则从错误队列中移除该请求。
    • 如果仍然失败,则保留该请求并增加其重试次数。
  5. API 调用 (apiCall 方法):

    • 模拟 API 调用,返回成功或失败的结果。

在这个类中,我们定义了一个 BatchRequestHandler 类,它接受两个参数:最大批次大小 (maxBatchSize) 和最大重试次数 (maxRetries)。该类包含了以下方法:

  • addRequests(urls):用于添加待处理的URL列表。
  • processRequests():用于开始处理请求队列中的请求。
  • processNextBatch():用于处理下一批请求。
  • retryErrors():用于处理错误队列中的请求。
  • apiCall(url):用于模拟API调用。

关键改动点解释:

  1. 指数退避重试机制:

    • 在每次重试之前,根据重试次数计算延迟时间。
    • 使用公式 Math.min(this.maxDelayMs, this.initialDelayMs * Math.pow(2, retries)) 来计算延迟时间。
      • initialDelayMs 是初始延迟时间(例如1秒)。
      • maxDelayMs 是最大的延迟时间(例如1分钟)。
      • retries 是当前请求的重试次数。
    • 通过指数增长的方式来逐渐增加重试之间的等待时间,以避免短时间内大量重试请求。
  2. 最大延迟时间:

    • 通过设置 maxDelayMs 参数来限制最长的延迟时间,防止无限增长。
  3. 随机延迟:

    • apiCall 方法中,我们使用了随机延迟来模拟API请求,这样可以在真实的场景中帮助分散请求,减少服务器压力。

注意事项:

  • 在实际应用中,您可能还需要考虑更多的错误处理逻辑,比如对特定类型的错误进行重试,或者在某些情况下完全放弃重试。
  • 实际部署时,可能还需要考虑并发限制、网络延迟等因素。