
在分布式系统和微服务架构中,应用程序通过网络调用外部 API 是常态。然而,网络环境的不稳定、服务瞬时过载、异步任务处理时间不确定等因素,都可能导致 API 请求失败或返回非最终状态。为了提高系统的鲁棒性和用户体验,实现一个智能的 API 请求重试机制显得尤为重要。特别是在需要轮询一个异步任务状态直至其完成的场景中,条件式重试更是不可或缺。
考虑这样一个场景:我们向一个文档处理服务发起请求,该服务会返回一个处理中的状态,并需要我们持续查询其状态,直到 response.data.status 字段的值变为 "done"。这要求我们的客户端代码能够:
最初的重试思路可能非常直接,例如使用递归函数。以下是一个基于 axios 的简化递归重试示例:
const axios = require("axios");
const qs = require("qs");
// 假设 apiKey 已定义
const apiKey = "YOUR_API_KEY";
let data = qs.stringify({
document_key: "038A2E0792CE72020E9BB88380D002EB582A6B3AE5883C34DE53C9F17D415D99",
});
let config = {
method: "post",
maxBodyLength: Infinity,
url: "https://api-free.deepl.com/v2/document/95BA71197AC66EE4745FF5269CF4399D",
headers: {
Authorization: apiKey,
"Content-Type": "application/x-www-form-urlencoded",
},
data: data,
};
function retryUntilSuccess(requestConfig, callback) {
axios
.request(requestConfig)
.then((response) => {
// 检查特定条件
if (response.data.status === "done") {
console.log("任务完成:", response.data.status);
callback(null, response.data); // 任务完成,返回结果
} else {
console.log("任务进行中,继续重试:", response.data.status);
// 未完成,继续重试
retryUntilSuccess(requestConfig, callback);
}
})
.catch((error) => {
console.error("请求失败,继续重试:", error.message);
// 请求失败,直接重试
retryUntilSuccess(requestConfig, callback);
});
}
// 调用重试函数
// retryUntilSuccess(config, function(err, result) {
// if (err) {
// console.error("最终失败:", err);
// } else {
// console.log("最终成功结果:", result);
// }
// });这种基础实现存在几个显著局限性:
为了克服上述局限性,我们需要一个更完善的重试策略,包括引入延迟、设置最大重试次数以及利用 async/await 简化异步流程。
以下是一个采用 async/await、支持延迟和最大重试次数的通用重试函数:
const axios = require("axios");
const qs = require("qs");
// 假设 apiKey 已定义
const apiKey = "YOUR_API_KEY";
let data = qs.stringify({
document_key: "038A2E0792CE72020E9BB88380D002EB582A6B3AE5883C34DE53C9F17D415D99",
});
let config = {
method: "post",
maxBodyLength: Infinity,
url: "https://api-free.deepl.com/v2/document/95BA71197AC66EE4745FF5269CF4399D",
headers: {
Authorization: apiKey,
"Content-Type": "application/x-www-form-urlencoded",
},
data: data,
};
/**
* 异步延迟函数
* @param {number} ms - 延迟毫秒数
*/
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
/**
* 执行带重试机制的 API 请求
* @param {object} requestConfig - axios 请求配置
* @param {function} conditionFn - 判断是否成功的函数,接收响应数据作为参数,返回布尔值
* @param {object} options - 重试选项
* @param {number} [options.maxRetries=5] - 最大重试次数
* @param {number} [options.initialDelayMs=1000] - 初始重试延迟(毫秒)
* @param {number} [options.backoffFactor=2] - 延迟因子,用于指数退避
* @returns {Promise<object>} - 成功时的响应数据
* @throws {Error} - 达到最大重试次数或遇到不可恢复错误时抛出
*/
async function retryApiRequest(requestConfig, conditionFn, options = {}) {
const { maxRetries = 5, initialDelayMs = 1000, backoffFactor = 2 } = options;
let currentDelay = initialDelayMs;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`尝试请求 (第 ${attempt} 次)...`);
const response = await axios.request(requestConfig);
// 检查业务逻辑条件
if (conditionFn(response.data)) {
console.log(`请求成功,条件满足!状态: ${response.data.status}`);
return response.data; // 条件满足,返回结果
} else {
console.log(`条件未满足,状态: ${response.data.status},将在 ${currentDelay}ms 后重试。`);
}
} catch (error) {
console.error(`请求失败 (第 ${attempt} 次): ${error.message}`);
// 判断是否为可重试错误(例如网络错误、5xx 服务器错误)
// 这里的判断可以更细致,例如根据 error.response.status
if (attempt === maxRetries || !axios.isAxiosError(error) || (error.response && error.response.status < 500)) {
// 如果是最后一次尝试,或者是非 axios 错误,或者客户端错误(4xx),则不再重试
throw new Error(`API 请求最终失败或条件未满足,已达最大重试次数 ${maxRetries} 次。`);
}
}
// 等待一段时间后重试
await delay(currentDelay);
// 指数退避:每次重试延迟时间翻倍
currentDelay *= backoffFactor;
}
// 如果循环结束仍未成功,则抛出错误
throw new Error(`API 请求最终失败或条件未满足,已达最大重试次数 ${maxRetries} 次。`);
}
// 定义成功条件函数
const isStatusDone = (data) => data && data.status === "done";
// 使用示例
(async () => {
try {
const result = await retryApiRequest(config, isStatusDone, {
maxRetries: 10,
initialDelayMs: 500, // 初始延迟 0.5 秒
backoffFactor: 1.5, // 每次延迟增加 1.5 倍
});
console.log("最终成功结果:", result);
} catch (error) {
console.error("教程最终失败:", error.message);
}
})();除了上述基础重试策略,在生产环境中还应考虑以下高级实践:
指数退避 (Exponential Backoff): 如示例所示,每次重试的延迟时间以指数级增长(例如 1s, 2s, 4s, 8s...)。这能有效避免在服务故障时瞬时产生大量重试请求,给服务恢复留出时间。
抖动 (Jitter): 在指数退避的基础上,引入随机性延迟。例如,将延迟时间设置为 (currentDelay * backoffFactor) * (1 + random_factor)。这可以防止大量客户端在同一时刻重试,进一步分散请求压力,避免“惊群效应”。
熔断器模式 (Circuit Breaker Pattern): 当某个服务持续失败时,熔断器会“打开”,阻止所有对该服务的后续请求,直接返回失败,而不是继续重试。经过一段时间后,熔断器会进入“半开”状态,允许少量请求通过以测试服务是否恢复。这可以防止失败的服务拖垮整个系统。像 opossum 这样的库可以帮助实现熔断器。
请求幂等性 (Idempotency): 重试请求可能会导致同一个操作被执行多次。确保你的 API 设计是幂等的,即多次执行相同的请求与一次执行的效果是相同的,这对于重试机制至关重要。例如,创建订单的请求应该包含一个唯一的事务 ID,以防止重复创建。
日志记录 (Logging): 详细记录每次重试的状态、耗时、错误信息等,有助于问题排查和系统监控。
实现健壮的 API 请求重试机制是构建可靠分布式系统的关键一环。通过结合 async/await、延迟、最大重试次数、指数退避等策略,我们可以有效地应对各种网络和服务瞬时故障。同时,结合熔断器、幂等性设计和完善的日志记录,能够进一步提升系统的稳定性和可维护性。在实际开发中,应根据具体的业务场景和对可靠性的要求,选择并优化最适合的重试策略。
以上就是API 请求条件式重试机制:实现与优化的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号