熔斷器模式可以防止應用程序不斷地嘗試執行可能會失敗的操作,使得應用程序繼續執行而不用等待修正錯誤,或者浪費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
下游服務的報警需要設置好