如何设计Nodejs的重试任务


highlight: a11y-dark
theme: channing-cyan

前言

前一篇文章中:

我们介绍了在nodejs中如何实现简单的定时任务,并且指出了定时器的缺陷。最后我们推荐了Nodejs中常见的任务调度框架,根据作者的个人经验推荐了Bree来作为上手框架。

假如你还没有看过之前的文章可以点击,这样你会对Nodejs的定时任务有更好的认知。

如何设计Nodejs的定时任务

这篇文章中,我们将会介绍如何设计Nodejs的重试任务。

重试任务是什么

在 Node.js 中,重试任务是指在执行某些任务时,如果任务失败,则在一段时间之后再次尝试执行任务的过程。

重试任务设计过程中最为关键的是重试的策略,包括但不限于

  1. 固定时间间隔

  2. 指数退避

  3. 自定义策略

为什么关注重试任务

在实际工作中,经常会遇到网络连接中断、依赖的服务出现故障等情况,这些情况会导致任务执行失败。

使用重试任务可以帮助程序应对临时性的故障,提高程序的可用性和稳定性。

当然对于存在持久性故障,重试是没有太大的帮助的。

如何设计Nodejs的重试任务

固定时间间隔

迭代法

async function retryTask(task, retryInterval, retryCount) {
  for (let i = 0; i < retryCount; i++) {
    try {
      await task();
      break; 
    } catch (error) {
      console.log(`第${i}次任务失败: ${error.message}. ${retryInterval}ms 后重试.`);
      await new Promise(resolve => setTimeout(resolve, retryInterval)); // 延时
    }
  }
}

在上面的代码中,重试任务的函数接收三个参数:

  1. task 是要重试的任务函数
  2. retryInterval 是重试间隔的时间。
  3. retryCount是尝试的总次数

在循环中,每次执行任务时都会使用 try/catch 语句进行捕获错误。

如果执行成功,则退出循环;

如果执行失败,则在一段时间之后再次尝试执行任务。

迭代法代码简单易懂,但是定制化程度不高。

递归法

async function retryTask(task, retryInterval, retryCount) {
    if(retryCount <= 0) {
        return 0;
    }
    try {
      await task();
    } catch (error) {
      console.log(`第${i}次任务失败: ${error.message}. ${retryInterval}ms 后重试.`);
      setTimeout(async () => {
          await retryTask(task, retryInterval, retryCount-1)
      }, retryInterval)
    }
}

在上面的代码中,重试任务的函数接收三个参数:

  1. task 是要重试的任务函数
  2. retryInterval 是重试间隔的时间。
  3. retryCount是尝试的总次数

在循环中,每次执行任务时都会使用 try/catch 语句进行捕获错误。

如果执行成功,函数结束。

如果执行失败,则在一段时间之后再次递归执行任务,并且更新参数。

递归法容易定制出符合业务需求的重试策略,但是新手容易无限递归。

指数退避

什么是指数退避

指数退避是一种重试策略,其中每次重试之间等待的时间呈指数级增加。这种策略可以在每次重试时等待时间较长的同时,避免重试操作过于频繁。

指数退避的公式如下:

Math.min(random * minTimeout * Math.pow(factor, attempt), maxTimeout)
  • retries: 重试的次数.
  • factor: 指数因子
  • minTimeout: 开始第一次尝试的时间
  • maxTimeout: 两次尝试的之间的最大时间
  • randomize: 随机因子

如果想详细了解为什么使用指数退避,请阅读下面这篇文章

个人感觉使用这个算法能避免多个客户端同时请求,造成对服务器的冲击。

重试框架——node-retry

个人实现指数退避策略确实比较麻烦,下面为大家介绍一个实现了指数退避的开源库:node-retry

具体如何使用这个库,我建议大家直接看官方文档。我给下面代码添加一些注释

const retry = require('retry')
const delay = require('delay')

const isItGood = [false, false, true]
let numAttempt = 0

function retryer() {
  let operation = retry.operation({
      retries: 5,
      factor: 3,
      minTimeout: 1 * 1000,
      maxTimeout: 60 * 1000,
      randomize: true,
  })

  return new Promise((resolve, reject) => {
    operation.attempt(async currentAttempt => {
      console.log('Attempt #:', numAttempt)
      await delay(2000)

      const err = !isItGood[numAttempt] ? true : null
      if (operation.retry(err)) {
        numAttempt++
        return
      }

      if (isItGood[numAttempt]) {
        resolve('All good!')
      } else {
        reject(operation.mainError())
      }
    })
  })
}

async function main() {
  console.log('Start')
  await retryer()
  console.log('End')
}

main()

首先,使用 retry.operation() 创建了一个重试操作,并且配置了重试策略。

在这个示例中,设置了重试次数为 5,增长因子为 3,最小等待时间为 1s,最大等待时间为 60s,启用了随机延迟。然后使用 operation.attempt() 方法实现重试机制。

当出错情况下,或者其他需要重试的情况下,调用 operation.retry(err) 方法进行重试。

假如正常执行完毕,调用 resolve(‘All good!’) 确定promise状态。

假如不想重试,可以可以直接reject即可。

自定义策略

我下面提供一个框架,来让大家自己实现自己的策略。

可以定制的是重试时间间隔和并且判断何时结束。


function retryInterval(retryCount) {
    let timeout = retryCount * 10 * 1000
    return timeout
}

function end(retryCount) {
    return retryCount <= 0
}
async function retryTask(task,retryCount) {
    if(end(retryCount)) {
        return 0;
    }
    try {
      await task();
    } catch (error) {
      console.log(`第${i}次任务失败: ${error.message}. ${retryInterval}ms 后重试.`);
      setTimeout(async () => {
          await retryTask(task,retryCount-1)
      }, retryInterval(retryCount-1))
    }
}

首先,定义了一个 retryInterval 函数,用于计算下一次重试之间的等待时间。这里面的策略是具体的等待时间为重试次数乘以 10s。

其次,定义一个end函数,用于判断何种条件下进行结束。这里面的策略是重试次数为小于等于0。

在函数中,使用 try/catch 语句捕获错误。
如果任务执行成功,则不执行任何操作。
如果任务执行失败,就开始重试。

总结

在这篇文章中,我们介绍了重试任务的应用场景。

介绍了三种重试策略的实现:

  1. 固定时间间隔

  2. 指数退避

  3. 自定义策略

其中重点介绍了指数退避策略,说明这个算法的优点和缺点。

最后,我们介绍了简单的自定义策略的代码框架,供大家使用。

最后的话

文章中的知识都是小弟学习过程中调研的笔记。各位大佬如果发现有啥纰漏,多多指出。学习过程中,希望大家多多反馈,有啥问题可以私信我。谢谢大家

exported (6).jpg

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容