倘若系統發生部分的故障時,是否有辦法維持正常運作? Moleculer 內建了一些容錯功能,可以在 Broker 選項中啟用或關閉。
Moleculer 內建了一個斷路器機制。它設置了多個門檻值,可以在時間視窗內檢查失敗的請求機率,當機率到達指定的門檻值,它就會使斷路器跳閘。如果啟用斷路器,所有的服務呼叫都將接受斷路器的保護。
關於斷路器相關說明,可參考 CircuitBreaker[2] 及斷路器模式[3] 的文章。
範例:在 Boker 選項中啟用
名稱 | 類型 | 預設值 | 說明 |
---|---|---|---|
enabled |
<Boolean> | false |
啟用斷路器 |
threshold |
<Number> | 0.5 |
門檻值。0.5 表示 50% 的機率。 |
minRequestCount |
<Number> | 20 |
最小請求數。在此請求數以下不會跳閘。 |
windowTime |
<Number> | 60 |
時間視窗的秒數。 |
halfOpenTime |
<Number> | 10000 |
由 open 切換到 half-open 狀態所需的毫秒數 |
check |
<Function> | err && err.code >= 500 |
用來檢查錯誤請求的函數 |
const broker = new ServiceBroker({
circuitBreaker: {
enabled: true,
threshold: 0.5,
minRequestCount: 20,
windowTime: 60, // 秒
halfOpenTime: 5 * 1000, // 毫秒
check: err => err && err.code >= 500
}
});
當斷路器的狀態改變時,
ServiceBroker
將會發送$circuit-breaker
事件,請參考事件章節。
範例:在 Actions 中覆蓋全域設定
users.service.js
module.export = {
name: "users",
actions: {
create: {
circuitBreaker: {
// 所有的斷路器選項都可以被覆蓋
threshold: 0.3,
windowTime: 30
},
handler(ctx) { }
}
}
};
Moleculer 有一個使用指數的迴避重試機制,可以嘗試重新呼叫失敗的請求。
名稱 | 類型 | 預設值 | 說明 |
---|---|---|---|
enabled |
<Boolean> | false |
啟用重試機制 |
retries |
<Number> | 5 |
重試次數 |
delay |
<Number> | 100 |
首次延遲時間。(毫秒) |
maxDelay |
<Number> | 2000 |
最大延遲時間。(毫秒) |
factor |
<Number> | 2 |
延遲的迴避因子。設為 1 則不做指數上升,設為 2 以上會由此指數,根據重試次數進行指數上升。 |
check |
<Function> | err && !!err.retryable |
用來檢查錯誤請求的函數 |
const broker = new ServiceBroker({
retryPolicy: {
enabled: true,
retries: 5,
delay: 100,
maxDelay: 2000,
factor: 2,
check: err => err && !!err.retryable
}
});
範例:呼叫時覆蓋全域設定
broker.call("posts.find", {}, { retries: 3 });
範例:在 Actions 中覆蓋全域設定
users.service.js
module.export = {
name: "users",
actions: {
find: {
retryPolicy: {
// 所有的重試選項都可以被覆蓋
retries: 3,
delay: 500
},
handler(ctx) { }
},
create: {
retryPolicy: {
// 關閉此 Action 的重試機制
enabled: false
},
handler(ctx) { }
}
}
};
回朔到前面 Broker 章節我們談過可以在 Broker 設定逾時。它可以在 ServiceBroker
選項或 broker.call
中覆蓋設定。在設定了逾時後若發生逾時事件, Broker 可以拋出一個 RequestTimeoutError
錯誤。
範例:在 Broker 選項中啟用
const broker = new ServiceBroker({
requestTimeout: 5 * 1000 // 毫秒
});
範例:在 broker.call
時覆蓋掉全域設定
broker.call("posts.find", {}, { timeout: 3000 });
Moleculer 使用分散式逾時[4] 。在巢狀呼叫的情況下,逾時的值會隨著經過的時間減少。如果逾時值小於等於零會拋出
RequestTimeoutError
錯誤,而下一個巢狀函數在呼叫時,會因為前一個逾時錯誤而拋出RequestSkippedError
錯誤。
隔離模式就像船艙的水密門,可以將故障的服務隔離以維持服務穩定。 Moleculer 框架實作了隔離功能,用於控制 Actions 的併發請求處理。
名稱 | 類型 | 預設值 | 說明 |
---|---|---|---|
enabled |
<Boolean> | false |
啟用隔離功能 |
concurrency |
<Number> | 3 |
最大併發執行次數 |
maxQueueSize |
<Number> | 10 |
最大隊列大小 |
範例:
const broker = new ServiceBroker({
bulkhead: {
enabled: true,
concurrency: 3,
maxQueueSize: 10,
}
});
concurrency
值可以限制併發請求執行次數。如果maxQueueSize
設定大於零,所有的運算資源又被占滿,那麼 Broker 就會將請求儲存在隊列中。若隊列大小超過maxQueueSize
的限制時, Broker 將會對超出的請求拋出QueueIsFull
錯誤。
範例:在 Actions 中覆蓋全域設定
users.service.js
module.export = {
name: "users",
actions: {
find: {
bulkhead: {
// 關閉此 Action 的隔離機制
enabled: false
},
handler(ctx) {}
},
create: {
bulkhead: {
// 增加這個 Action 的併發數
concurrency: 10
},
handler(ctx) {}
}
}
};
範例:在事件中覆蓋全域設定
my.service.js
module.exports = {
name: "my-service",
events: {
"user.created": {
bulkhead: {
enabled: true,
concurrency: 1
},
async handler(ctx) {
// ...
}
}
}
}
當錯誤發生時,如果你不想將錯誤響應給使用者, Fallback 可以呼叫其它 Actions 或是返回一些通用內容。你可以在 broker.call
的選項中定義 fallbackResponse
函數來返回任何的內容,但它必須是一個 Promise 函數。 Broker 會將 Context 與錯誤作為參數傳遞給 Fallback 函數。
範例:在 broker.call
選項設置 fallback 響應
const result = await broker.call("users.recommendation", { userID: 5 }, {
timeout: 500,
fallbackResponse(ctx, err) {
// 由快取返回一個通用響應
return broker.cacher.get("users.fallbackRecommendation:" + ctx.params.userID);
}
});
範例:在 Actions 定義 fallback 函數
注意,當錯誤發生在 Action 的處理階段才會使用此 fallback 函數,如果在呼叫遠端節點時發生錯誤(例如逾時),則不使用此 fallback 函數。這種情況下請改在
broker.call
選項中設置 fallback 函數。
module.exports = {
name: "recommends",
actions: {
add: {
fallback: (ctx, err) => "Some cached result",
handler(ctx) {
// ...
}
}
}
};
範例:將 fallback 函數放在 method ,並以字串名稱設定 fallback 函數
module.exports = {
name: "recommends",
actions: {
add: {
// 當錯誤發生時呼叫 `getCachedResult` 方法
fallback: "getCachedResult",
handler(ctx) {
// ...
}
}
},
methods: {
getCachedResult(ctx, err) {
return "Some cached result";
}
}
};
[1] Fault tolerance, https://moleculer.services/docs/0.14/fault-tolerance.html
[2] CircuitBreaker, https://martinfowler.com/bliki/CircuitBreaker.html
[3] Circuit Breaker pattern, https://docs.microsoft.com/azure/architecture/patterns/circuit-breaker
[4] Resilience for distributed systems, https://www.getambassador.io/learn/service-mesh/resilience-for-distributed-systems