前端重试策略 - 指数退避算法

·  阅读 2085
前端重试策略 - 指数退避算法

前言

  在日常的开发过程中,由于接口的不稳定性,又或者是网络抖动导致的单次接口请求失败,需要前端通过重试策略,来解决系统不可用的问题。

  接下来我们一起探索前端最佳的重试策略。

基础实现

  对于基础扎实的前端同学来说,可以很容易地想到如下实现:

/**
 * 重试方法
 * @param {Function} fn 异步函数
 * @param {number} times 重试次数
 * @param {number} timeout 重试等待时间
 * @returns 
 */
function retry (fn, times, timeout{
  return new Promise((resolve, reject) => {
    const attemp = () => {
      fn().then(resolve).catch(err => {
        if (times === 0) {
          return reject(err)
        }
        times--;
        setTimeout(attemp, timeout);
      });
    }

    attemp();
  });
}

retry(fetchData, 31000);
复制代码

  上述代码基于 Promise 机制,通过拦截异步请求的 reject 状态,完成异步请求失败的重试操作。

  整体的实现逻辑是没啥问题的,但是一个优秀的重试机制需要考虑如何设置一个适当的重试等待时间。

指数退避算法

  指数退避算法正是解决如何设置适当的重试等待时间的算法,它的处理流程如下:

  • 客户端发起网络请求。
  • 如果请求失败,等待 1 + random_number_milliseconds 秒之后再重试请求。
  • 如果请求失败,等待 2 + random_number_milliseconds 秒之后再重试请求。
  • 如果请求失败,等待 4 + random_number_milliseconds 秒之后再重试请求。
  • 依此类推,等待时间的上限为 maximum_backoff。

  在指数退避算法中重试等待时间为:

  Math.min((2 ** n + random_number_milliseconds), maximum_backoff)
复制代码

  这里的 random_number_milliseconds 是小于或等于 1000 的毫秒数(随机值)。这有助于避免出现以下情况:许多客户端同步进行处理并同时执行重试操作,导致同步发送每一波请求。每次重试请求后,系统都会重新计算 random_number_milliseconds 值。

/**
 * 生成重试等待时间
 * @param {number} times 重试次数
 * @param {number} maximum_backoff 最大等待秒数
 * @returns 
 */
function createTimeout(times, maximum_backoff) {
  const random_number_milliseconds = Math.floor(Math.random() * 1000);
  return Math.min(Math.pow(2, times) * 1000 + random_number_milliseconds, maximum_backoff);
}
复制代码

  根据算法描述,可以实现上述函数来生成重试等待时间。

  接下来,就可以在进行网络请求之前生成重试等待时间列表,以便后续重试使用。

const maximum_backoff = 64 * 1000;
function retry (fn, times) {
  const operationTimeout = [];
  for (let i = 0; i < times; i++) {
    operationTimeout.push(createTimeout(i, maximum_backoff));
  }

  return new Promise((resolve, reject) => {
    const attemp = () => {
      fn().then(resolve).catch(err => {
        if (times === 0) {
          return reject(err)
        }
        times--;
        setTimeout(attemp, operationTimeout.shift());
      });
    }

    attemp();
  });
}
复制代码

  达到 maximum_backoff 时间后可以选择继续重试,但是如果请求系统处于长时间不可用的状态,客户端重试再多次都是没有意义的,所以可以设置一个 deadline 时间上限,达到上限之后不再重试。

const maximum_backoff = 64 * 1000;
function retry (fn, times, deadline) {
  const operationTimeout = [];
  for (let i = 0; i < times; i++) {
    if (deadline < 0) {
      break;
    }
    const timeout = createTimeout(i, maximum_backoff);
    deadline -= timeout;
    operationTimeout.push(timeout);
  }

  times = operationTimeout.length;

  return new Promise((resolve, reject) => {
    const attemp = () => {
      fn().then(resolve).catch(err => {
        if (times === 0) {
          return reject(err)
        }
        times--;
        setTimeout(attemp, operationTimeout.shift());
      });
    }

    attemp();
  });
}
复制代码

总结

  以上就是本文的全部内容,希望能够给你带来帮助,欢迎关注点赞转发

  参考文档:https://cloud.google.com/storage/docs/retry-strategy?hl=zh-cn

分类:
前端
收藏成功!
已添加到「」, 点击更改