使用指数回退算法重试请求

我们在搭建自己的服务的时候,同时会用到一些第三方服务的api,这些api可能会有速率的限制,这个时候,我们就需要控制我们的发送请求的速率,有很多种方法,比如我们直接计算出来每秒最多请求几次,然后做一些精细化的控制,当然我们也有比较简单粗暴但是行之有效的方法,就是等一会儿再请求,这就是指数回退算法。

算法实现

下面是使用 TypeScript 实现的函数,该函数可以使用指数回退算法重试抛出指定错误的请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
async function retryWithExponentialBackoff<T>(request: () => Promise<T>, errorType: any, maxRetries: number, initialDelay: number, maxDelay: number): Promise<T> {
let retries = 0;
let delay = initialDelay;

while (retries < maxRetries) {
try {
const result = await request();
return result;
} catch (error) {
if (error instanceof errorType) {
console.log(`Retry attempt ${retries + 1} failed with error: ${error.message}`);

if (retries === maxRetries - 1) {
throw error;
}

await new Promise(resolve => setTimeout(resolve, delay));
delay = Math.min(delay * 2, maxDelay);
retries++;
} else {
throw error;
}
}
}

throw new Error(`Max retries (${maxRetries}) exceeded.`);
}

这个函数接受以下参数:

  • request:一个返回 Promise 的函数,表示要重试的请求。
  • errorType:要捕获并重试的错误类型。
  • maxRetries:最大重试次数。
  • initialDelay:初始延迟时间(毫秒)。
  • maxDelay:最大延迟时间(毫秒)。

函数会在每次请求失败时捕获指定的错误类型,并使用指数回退算法进行重试。重试的次数将根据 maxRetries 参数确定,初始延迟时间为 initialDelay,每次重试后的延迟时间将加倍,但不会超过 maxDelay

如果达到最大重试次数仍然失败,函数将抛出一个错误。

以下是一个示例用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 模拟一个可能会失败的请求
async function makeRequest(): Promise<number> {
const random = Math.random();
if (random < 0.5) {
throw new Error('Request failed');
}
return 42;
}

// 使用指数回退算法重试请求
retryWithExponentialBackoff(makeRequest, Error, 3, 1000, 5000)
.then(result => console.log(`Request succeeded with result: ${result}`))
.catch(error => console.error(`Request failed after retries: ${error.message}`));

在上面的示例中,makeRequest 函数模拟了一个可能会失败的请求。我们使用 retryWithExponentialBackoff 函数对该请求进行重试,最大重试次数为 3,初始延迟时间为 1000 毫秒,最大延迟时间为 5000 毫秒。

如果请求成功,将输出 Request succeeded with result: 42;如果重试次数达到上限仍然失败,将输出类似于 Request failed after retries: Max retries (3) exceeded. 的错误信息。

优缺点

优点比较明显,就是简单,重试就完事了,不过每次重试失败都会让下一次的重试来得更晚,就导致了没有可能会有“饿死”的现象,也没有办法通过优先级去控制