Kitex 提供了熔斷器的實現(xiàn),但是沒有默認開啟,需要用戶主動使用。
下面簡單介紹一下如何使用以及 Kitex 熔斷器的策略。
// build a new CBSuite
cbs := circuitbreak.NewCBSuite(GenServiceCBKeyFunc)
// add to the client options
opts = append(opts, client.WithCircuitBreaker(cbs))
// init client
cli, err := xxxservice.NewClient(targetService, opts)
Kitex 大部分服務(wù)治理模塊都是通過 middleware 集成,熔斷也是一樣。Kitex 提供了一套 CBSuite,封裝了服務(wù)粒度的熔斷器和實例粒度的熔斷器。
- 服務(wù)粒度熔斷:
按照服務(wù)粒度進行熔斷統(tǒng)計,通過 WithMiddleware 添加。服務(wù)粒度的具體劃分取決于 Circuit Breaker Key,既熔斷統(tǒng)計的 key,初始化 CBSuite 時需要傳入 **GenServiceCBKeyFunc**,默認提供的是 circuitbreaker.RPCInfo2Key ,該 key 的格式是 `fromServiceName/toServiceName/method`,即按照方法級別的異常做熔斷統(tǒng)計。
- 實例粒度熔斷
按照實例粒度進行熔斷統(tǒng)計,主要用于解決單實例異常問題,如果觸發(fā)了實例級別熔斷,框架會自動重試。
注意,框架自動重試的前提是需要通過 **WithInstanceMW** 添加,WithInstanceMW 添加的 middleware 會在負載均衡后執(zhí)行。
默認的熔斷閾值是 ErrRate: 0.5, MinSample: 200,錯誤率達到 50% 觸發(fā)熔斷,同時要求統(tǒng)計量 >200。若要調(diào)整閾值,調(diào)用 CBSuite 的 UpdateServiceCBConfig 和 UpdateInstanceCBConfig 來更新 Key 的閾值。
在進行 RPC 調(diào)用時,下游服務(wù)難免會出錯;
當下游出現(xiàn)問題時,如果上游繼續(xù)對其進行調(diào)用,既妨礙了下游的恢復(fù),也浪費了上游的資源;
為了解決這個問題,你可以設(shè)置一些動態(tài)開關(guān),當下游出錯時,手動的關(guān)閉對下游的調(diào)用;
然而更好的辦法是使用熔斷器,自動化的解決這個問題。
這里是一篇更詳細的熔斷器介紹。
比較出名的熔斷器當屬 hystrix 了,這里是它的設(shè)計文檔。
熔斷器的思路很簡單:根據(jù) RPC 的成功失敗情況,限制對下游的訪問;
通常熔斷器分為三個時期: CLOSED、OPEN、HALFOPEN;
RPC 正常時,為 CLOSED;
當 RPC 錯誤增多時,熔斷器會被觸發(fā),進入 OPEN;
OPEN 后經(jīng)過一定的冷卻時間,熔斷器變?yōu)?nbsp;HALFOPEN;
HALFOPEN 時會對下游進行一些有策略的訪問,然后根據(jù)結(jié)果決定是變?yōu)?nbsp;CLOSED,還是 OPEN;
總的來說三個狀態(tài)的轉(zhuǎn)換大致如下圖:
[CLOSED] ---> tripped ----> [OPEN]<-------+
^ | ^
| v |
+ | detect fail
| | |
| cooling timeout |
^ | ^
| v |
+--- detect succeed --<-[HALFOPEN]-->--+
Kitex 默認提供了三個基本的熔斷觸發(fā)策略:
當然,你可以通過實現(xiàn) TripFunc 函數(shù)來寫自己的熔斷觸發(fā)策略;
Circuitbreaker 會在每次 Fail 或者 Timeout 時,去調(diào)用 TripFunc,來決定是否觸發(fā)熔斷;
進入 OPEN 狀態(tài)后,熔斷器會冷卻一段時間,默認是 10 秒,當然該參數(shù)可配置 (CoolingTimeout);
在這段時期內(nèi),所有的 IsAllowed() 請求將會被返回 false;
冷卻完畢后進入 HALFOPEN;
在 HALFOPEN 時,熔斷器每隔 " 一段時間 " 便會放過一個請求,當連續(xù)成功 " 若干數(shù)目 " 的請求后,熔斷器將變?yōu)?nbsp;CLOSED; 如果其中有任意一個失敗,則將變?yōu)?nbsp;OPEN;
該過程是一個逐漸試探下游,并打開的過程;
上述的 " 一段時間 “(DetectTimeout) 和 " 若干數(shù)目 “(DEFAULT_HALFOPEN_SUCCESSES) 都是可以配置的;
熔斷器會統(tǒng)計一段時間窗口內(nèi)的成功,失敗和超時,默認窗口大小是 10S;
時間窗口可以通過兩個參數(shù)設(shè)置,不過通常情況下你可以不用關(guān)心 .
統(tǒng)計方法是將該段時間窗口分為若干個桶,每個桶記錄一定固定時長內(nèi)的數(shù)據(jù);
比如統(tǒng)計 10 秒內(nèi)的數(shù)據(jù),于是可以將 10 秒的時間段分散到 100 個桶,每個桶統(tǒng)計 100ms 時間段內(nèi)的數(shù)據(jù);
Options 中的 BucketTime 和 BucketNums,就分別對應(yīng)了每個桶維護的時間段,和桶的個數(shù);
如將 BucketTime 設(shè)置為 100ms,將 BucketNums 設(shè)置為 100,則對應(yīng)了 10 秒的時間窗口;
隨著時間的移動,窗口內(nèi)最老的那個桶會過期,當最后那個桶過期時,則會出現(xiàn)了抖動;
舉個例子:
如果使用分桶計數(shù)的辦法,這樣的抖動是無法避免的,比較折中的一個辦法是將桶的個數(shù)增多,可以降低抖動的影響;
如劃分 2000 個桶,則抖動對整體的數(shù)據(jù)的影響最多也就 1/2000; 在該包中,默認的桶個數(shù)也是 2000,桶時間為 5ms,總體窗口為 10S;
當時曾想過多種技術(shù)辦法來避免這種問題,但是都會引入更多其他的問題,如果你有好的思路,請 issue 或者 PR.
更多建議: