如何设计Nodejs的重试任务
前言
前一篇文章中:
我们介绍了在nodejs中如何实现简单的定时任务,并且指出了定时器的缺陷。最后我们推荐了Nodejs中常见的任务调度框架,根据作者的个人经验推荐了Bree来作为上手框架。
假如你还没有看过之前的文章可以点击,这样你会对Nodejs的定时任务有更好的认知。
这篇文章中,我们将会介绍如何设计Nodejs的重试任务。
重试任务是什么
在 Node.js 中,重试任务是指在执行某些任务时,如果任务失败,则在一段时间之后再次尝试执行任务的过程。
重试任务设计过程中最为关键的是重试的策略,包括但不限于
-
固定时间间隔
-
指数退避
-
自定义策略
为什么关注重试任务
在实际工作中,经常会遇到网络连接中断、依赖的服务出现故障等情况,这些情况会导致任务执行失败。
使用重试任务可以帮助程序应对临时性的故障,提高程序的可用性和稳定性。
当然对于存在持久性故障,重试是没有太大的帮助的。
如何设计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)); // 延时
}
}
}
在上面的代码中,重试任务的函数接收三个参数:
- task 是要重试的任务函数
- retryInterval 是重试间隔的时间。
- 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)
}
}
在上面的代码中,重试任务的函数接收三个参数:
- task 是要重试的任务函数
- retryInterval 是重试间隔的时间。
- 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 语句捕获错误。 如果任务执行成功,则不执行任何操作。 如果任务执行失败,就开始重试。
总结
在这篇文章中,我们介绍了重试任务的应用场景。
介绍了三种重试策略的实现:
-
固定时间间隔
-
指数退避
-
自定义策略
其中重点介绍了指数退避策略,说明这个算法的优点和缺点。
最后,我们介绍了简单的自定义策略的代码框架,供大家使用。
最后的话
文章中的知识都是小弟学习过程中调研的笔记。各位大佬如果发现有啥纰漏,多多指出。学习过程中,希望大家多多反馈,有啥问题可以私信我。谢谢大家
转载自:https://juejin.cn/post/7184069611820679228