学习如何使用重试、断路器和舱壁模式来构建更加健壮的 Node.js API,以优雅地处理故障并确保高可用性。
恢复能力
在分布式系统和微服务架构里,处理间歇性故障是不可避免的。这些问题会直接影响用户体验和系统可靠性。在使用 Node.js 构建 API 时,通过实施弹性模式,可以确保故障能够被可预测地处理,保持系统的稳健和功能。本文将介绍三种构建弹性 API 的基本模式:重试、断路器 和 隔离。
瑞典重启模式改正为:
瑞典重启模式改正为:
重试模式重试策略是一种简单的技术,当遇到失败时,它会尝试多次执行该操作,希望问题只是暂时的,并会在后续尝试中自动解决。这对于那些偶尔会出故障的外部 API 调用特别有用。
Node.js 实现中使用**axios-retry**
**: 为了演示,我们将使用 axios
库进行 HTTP 请求,并使用 axios-retry
来实现重试功能。
import axios from 'axios';
import axiosRetry from 'axios-retry';
axiosRetry(axios, { retries: 3, retryDelay: axiosRetry.exponentialDelay });
const fetchDataWithRetry = async () => {
try {
const { data } = await axios.get('https://api.example.com/data');
return data;
} catch (error) {
console.error('重试后仍然无法获取数据,错误信息为:', error.message);
}
};
在本例中,我们将 axios-retry
配置为最多重试三次,并在每次尝试之间应用指数级的延时。这可以防止系统在首次尝试失败后立即放弃,从而在偶发故障的情况下提高系统的韧性。
断路器
断路器模式就像一个电路断路器一样工作:它监控故障情况,并在达到阈值时“断开连接”,以阻止对已故障服务的进一步调用。这可以阻止系统发送可能失败的请求,从而减少过载并保护其他服务不受影响。
在Node.js中使用**opossum**
:opossum
是一个断路器库,它简化了在Node.js中实现这种模式的步骤。
import CircuitBreaker from 'opossum';
import axios from 'axios';
const getExternalData = async () => {
const { data } = await axios.get('https://api.example.com/data');
return data;
};
const breaker = new CircuitBreaker(getExternalData, {
timeout: 3000, // 最长操作时间
errorThresholdPercentage: 50, // 失败阈值(超过该阈值即“断开”电路)
resetTimeout: 5000 // 电路在“重新连接”前的等待时间
});
breaker.fallback(() => '服务暂时不可用,请稍后再试');
breaker
.触发()
.then(() => console.log)
.catch(() => console.error);
在这个示例中,如果超过50%的请求失败或耗时超过3秒,断路器会打开,并等待5秒后重试连接。我们还设定了一个在服务不可用时返回的备用信息。
舱壁模式(Bulkhead模式):一种将系统划分为多个独立部分的设计模式。舱壁模式将应用程序的不同部分隔离,以限制故障的影响。假设你有两个服务,一个很关键,另一个则没那么关键:通过使用这种模式,你可以为每个服务分配独立的资源,防止一个服务的故障影响到另一个服务的运行。
使用连接池实现:一种简单的Bulkhead方法是在Node.js中限制同时连接。我们将使用async
来限制并行请求。
import async from 'async';
import axios from 'axios';
// 创建一个异步队列,限制同时处理5个任务
// 限制同时请求数量为5个
const queue = async.queue(async ({ url }) => {
const { data } = await axios.get(url);
console.log('打印响应数据', data);
}, 5);
// 异步获取多个URL的函数
const fetchMultipleUrls = (urls) => {
urls.forEach(url => queue.push({ url }));
};
fetchMultipleUrls(['https://api.example1.com', 'https://api.example2.com']);
在这个例子中,我们将并发请求限制为最多五个,这样可以防止过载,避免出现瓶颈,确保即使其中一个 API 变慢,系统也能正常处理其他任务。
实际示例:组合模式示例我们可以通过展示这些韧性模式的实际情况,构建一个包含两个应用的完整示例。
- 服务API:此服务模拟了一个存在问题的API,该API会出现间歇性错误和延迟。
- 弹性客户端:主应用程序向此不稳定服务发起请求,并通过应用重试、断路器和隔离策略来处理故障。
此 API 模拟了一个响应可能不一致的服务。使用 Express,会偶尔返回错误,或延迟回复,延迟时间是随机的。
安装依赖:
请按照以下步骤操作:
创建一个名为 service-api 的目录
mkdir service-api
进入 service-api 目录
cd service-api
运行 npm init -y
初始化项目
npm init -y
安装 express 模块
npm install express
服务端API代码(service-api/index.js
):该文件包含了后端服务相关的API代码。
import express from 'express';
const app = express();
const PORT = 3001;
// 模拟间歇性失败
app.get('/unstable', (req, res) => {
const randomFail = Math.random() < 0.5; // 50%的失败率
const randomDelay = Math.floor(Math.random() * 4000); // 最多延迟4秒时间
if (randomFail) {
return res.status(500).json({ error: '间歇性服务错误' });
} else {
setTimeout(() => {
res.json({ message: '请求成功,延迟后返回' });
}, randomDelay);
}
});
app.listen(PORT, () => {
console.log(`控制台日志输出:服务API正在运行在 http://localhost:${PORT} 地址`);
});
这个 API 包含一个 /unstable
端点,这个端点具有以下功能:
- 50% 的时间返回 500 状态码的错误。
- 成功时,会在最多 4 秒的随机延迟后响应。
现在,我们来构建一个主要的应用程序,这个程序会使用重试、断路器和舱壁模式,来向服务API不太稳定的端点发起请求。
安装依赖:
mkdir resilient-client # 创建一个名为resilient-client的文件夹
cd resilient-client # 进入resilient-client文件夹
npm init -y # 使用默认设置初始化npm
npm install axios axios-retry opossum async # 安装axios、axios-retry、opossum和async这几个npm包
有弹性的客户端代码 (resilient-client/index.js)
import axios from 'axios';
import axiosRetry from 'axios-retry';
import CircuitBreaker from 'opossum';
import async from 'async';
// 重试配置使用axios-retry
axiosRetry(axios, { retries: 3, retryDelay: axiosRetry.exponentialDelay });
const fetchDataWithRetry = async (url) => {
try {
const { data } = await axios.get(url);
return data;
} catch (error) {
console.error('重试后仍无法获取数据,错误信息为', error.message);
}
};
// 创建断路器实例
const breaker = new CircuitBreaker(fetchDataWithRetry, {
timeout: 3000, // 最大操作时间
errorThresholdPercentage: 50, // 失败阈值百分比
resetTimeout: 5000 // 尝试关闭断路器的时间
});
breaker.fallback(() => '服务暂时无法访问');
// 使用async.queue来限制并发请求,最多5个请求
const queue = async.queue(async ({ url }) => {
try {
const result = await breaker.fire(url);
console.log(result);
} catch (error) {
console.error('获取数据时发生错误,错误信息为', error.message);
}
}, 5);
// 用于触发多个请求的函数,以便观察重试、断路器和隔离的实际效果
const fetchMultipleUrls = (urls) => {
urls.forEach(url => queue.push({ url }));
};
// 用于测试的URL,模拟实际请求
fetchMultipleUrls(Array(10).fill('http://localhost:3001/unstable'));
这段代码的解释:
- 重试模式:我们使用
axios-retry
在请求失败时最多重试三次,每次重试之间会有逐渐增加的延迟。 - 断路器模式:我们使用
opossum
配置断路器,在连续失败后“打开”电路,防止进一步的尝试以减少服务的负载。它会在5秒后自动恢复,允许重试。 - 舱壁模式:我们使用
async.queue
将并发请求的数量限制为五个,隔离并限制活跃请求的数量。
想看看这些模式怎么运作吗?请按照以下步骤操作:
启动一下服务API。在 service-api
目录下运行:
运行 `node index.js`
node index.js
运行韧性客户端:
在另一个终端窗口中,进入 resilient-client
文件夹并运行命令。
node index.js
请检查弹性客户端的终端日志,注意以下行为。
- 重试:当请求失败(500错误)时,应用程序将最多重试三次请求。
- 断路器:连续失败达到阈值后,断路器将“断路”,停止新的请求5秒并显示备用消息。
- 舱壁:限制同时请求数为五个,确保系统在高负载下仍保持响应。
这些日志会显示每个模式何时被激活以及它们如何配合以有效地应对来自不稳定API的错误。
最佳做法和最后的思考实施弹性模式是构建稳健、高可用系统的关键步骤。然而,这些模式需要仔细调优以避免副作用。以下是一些最佳实践和注意事项:
- 设置合理的重试限制和延迟间隔:过多的重试可能会使网络问题更加严重。配置重试次数和延迟间隔以避免在故障期间流量激增。
- 监控断路器状态:记录断路器的状态(开启、半开、关闭)有助于发现持久性故障并判断当前阈值是否合适。这也可以帮助识别系统范围问题的根本原因。
- 根据资源可用性调整隔离界限:隔离界限应与可用资源(如 CPU、内存)相匹配,以避免资源过载并确保关键服务保持可用。模拟压力测试有助于确定每个服务的最优并发限制。
- 备用方案和优雅降级:备用响应,如显示缓存数据或简化内容,能使服务即使在降级状态下也能继续运行。这些措施可以优雅地降级并保持基本服务。
- 定期测试弹性模式的配置:模拟网络故障、API 停机时间和高流量负载,确保弹性模式配置有效。持续测试有助于发现改进点并为实际问题做好准备。
总结
在今天的分布式系统中,构建可靠的API至关重要,其中微服务、第三方API和网络问题带来了独特的可靠性挑战,包括微服务、第三方API和网络问题。重试、断路器和隔离模式 提供了强大的解决方案,通过确保故障可以优雅地处理,服务保持响应,并使资源得到合理分配来缓解这些问题。
本文中,我们展示了在Node.js中实现这些模式的实用的实现方式,通过模拟一个出现故障的服务来展示每个模式在实时环境下的表现。通过仔细调整这些模式、监控它们并应用最佳实践,您可以显著增强应用程序的韧性,确保在不利条件下用户仍能有更流畅的体验。
这些模式为构建稳健的API提供了基础,通过采用它们,你可以创建不仅能够应对失败,还能继续稳定地提供可靠服务的系统。
点击这里访问仓库here
共同学习,写下你的评论
评论加载中...
作者其他优质文章