iT邦幫忙

2022 iThome 鐵人賽

DAY 17
2

容錯

倘若系統發生部分的故障時,是否有辦法維持正常運作? 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 錯誤。

隔離 (Bulkhead)

隔離模式就像船艙的水密門,可以將故障的服務隔離以維持服務穩定。 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

當錯誤發生時,如果你不想將錯誤響應給使用者, 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

家家酒小劇場

  • Otter - 為什麼微服務需要容錯機制呢?
  • Boxy - 因為服務被拆分成許多的服務,發生故障的機率也隨著提升,所以需要建立這麼多保護機制,來提升系統的可靠度唷。

上一篇
Day 16 : 負載平衡
下一篇
Day 18 : 快取
系列文
Moleculer 家家酒31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言