熔断器模式可以防止应用程序不断地尝试执行可能会失败的操作,使得应用程序继续执行而不用等待修正错误,或者浪费CPU时间去等到长时间的超时产生。熔断器模式也可以使应用程序能够诊断错误是否已经修正,如果已经修正,应用程序会再次尝试调用操作。
问题描述
假设我们有两个服务serviceA、serviceB,serviceA可以正常对外提供服务,某些API依赖serviceB。
- 当serviceB异常完全不可用时,serviceA会一直调用serviceB,返回报错信息,健壮一点的程序都会处理这些错误,不至于让服务崩溃。
- 当serviceB响应变慢,处理请求的能力变低时,这种情况下serviceA一直等待请求完成,最终会导致资源耗尽。
因此我们需要一个机制,当下游服务变得不可用(或者异常增多时),需要切断对下游服务的访问,直接返回错误值或者默认值
我们的熔断器应该要满足以下要求:
- 自定义回退:尝试从其他来源获取相同的数据如果不可能,请使用自己的缓存值。
- 快速失败:如果serviceA知道失败serviceB。了,那么就没有必要等待超时并消耗自己的资源它应该尽快返回“知道” serviceB已关闭
- 不要崩溃:正如我们在这种情况下看到的那样,serviceA不应该崩溃。
- 自动修复:定期检查是否serviceB再次起作用。
- 其他API应该有效:所有其他API应该继续工作。
熔断机制
我们使用brakes包来实现熔断机制
'use strict';
const Brakes = require('brakes');
const Promise = require('bluebird');
function promiseCall() {
return new Promise((resolve, reject) => {
if (Math.random() > 0.8) {
console.log('运行成功');
resolve('正常值');
} else {
console.error('运行失败');
reject(new Error());
}
});
}
function fallbackCall() { // 降级方案
return new Promise(resolve => {
resolve('降级方案返回错误值或缓存值');
});
}
function healthCheckCall() { // 健康检查
return new Promise((resolve, reject) => {
// this will return 20% true, 80% false
if (Math.random() < 0.8) {
console.log('\n健康检查通过');
resolve();
} else {
console.error('\n健康检查失败');
reject(new Error('health check failed'));
}
});
}
const brake = new Brakes(promiseCall, {
timeout: 150,
threshold: 0.5, // 成功请求比例阈值
waitThreshold: 10, // 等待对应请求数量之后统计成功请求比例
healthCheckInterval: 2000, // 健康检查间隔
});
brake.fallback(fallbackCall);
brake.healthCheck(healthCheckCall);
brake.on('circuitClosed', () => {
console.log('\n服务恢复');
});
brake.on('circuitOpen', () => {
console.log('\n服务熔断');
});
const main = async () => {
for (let i = 0; i < 2000; i++) {
try {
const result = await brake.exec();
await Promise.delay(300);
console.log(i, result);
} catch (error) {
console.error(i, error.message);
}
}
};
main().then(console.log).catch(console.error);
执行结果
// 开始时是正常运行的
运行成功
0 '正常值'
运行失败
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 '降级方案返回错误值或缓存值'
不用人工干预,自动实现服务异常时熔断、服务修复后自动恢复请求
建议在使用的时候直接封装在客户端对外调用逻辑里面,上层调用不需要知道有断路器的存在
One more thing
下游服务的报警需要设置好