go-zero熔斷機制組件Breaker接口定義使用解析
概述
熔斷機制是一種微服務(wù)保護機制。當(dāng)某個微服務(wù)出現(xiàn)故障或者異常時,通過熔斷機制,可以防止故障或異常擴散到整個微服務(wù)系統(tǒng)中,從而避免整個微服務(wù)系統(tǒng)被拖垮,熔斷機制的大致流程是(以下流程基于 go-zero 熔斷器為背景):
- 指標(biāo)采集:熔斷器通過不斷監(jiān)測服務(wù)的請求和響應(yīng)來收集指標(biāo),這些指標(biāo)包含但不限于響應(yīng)時間,錯誤率,超時率等。
- 閾值設(shè)定:熔斷器通過指標(biāo)來計算出閾值,當(dāng)指標(biāo)超過閾值時,熔斷器會打開。
- 熔斷狀態(tài)控制:一般熔斷器都有3個狀態(tài),熔斷開啟,熔斷恢復(fù),熔斷半開:
3.1 熔斷開啟:當(dāng)服務(wù)的指標(biāo)超過設(shè)定的閾值時,熔斷機制將服務(wù)切換到熔斷狀態(tài)。在熔斷狀態(tài)下,服務(wù)將停止接收新的請求,并立即返回錯誤響應(yīng),而不會進行實際的業(yè)務(wù)邏輯處理。
3.2 熔斷恢復(fù):一旦服務(wù)進入熔斷狀態(tài),熔斷機制會設(shè)定一個恢復(fù)時間窗口,在該窗口內(nèi)不再接收新的請求。在這段時間內(nèi),系統(tǒng)可以嘗試修復(fù)服務(wù)或等待服務(wù)自動恢復(fù)。
3.3 半開狀態(tài):在熔斷恢復(fù)時間窗口結(jié)束后,熔斷機制將服務(wù)切換到半開狀態(tài)。在半開狀態(tài)下,系統(tǒng)會嘗試發(fā)送一部分請求給服務(wù),以檢測其是否已經(jīng)恢復(fù)正常。如果這些請求成功響應(yīng),那么服務(wù)將被認為是恢復(fù)正常,并繼續(xù)接收新的請求。否則,服務(wù)將重新進入熔斷狀態(tài)。
但在 go-zero 中并非完全如此,go-zero 中只有熔斷關(guān)閉,熔斷觸發(fā)開啟狀態(tài),為什么是熔斷觸發(fā)開啟,在 go-zero 中,熔斷指標(biāo)達到設(shè)定閾值后,并不是直接攔截所有請求,而是有一定的概率攔截請求,這并不是說他就沒有熔斷恢復(fù)和熔斷半開狀態(tài),只是 go-zero 中熔斷器將這 2 個狀態(tài)巧妙的用滑動窗口來實現(xiàn)了。
go-zero 熔斷器時序圖
源碼解析
在 go-zero 中,Breaker 是一個接口,其接口定義如下:
Breaker interface { Name() string Allow() (Promise, error) Do(req func () error) error DoWithAcceptable(req func () error, acceptable Acceptable) error DoWithFallback(req func () error, fallback func (err error) error) error DoWithFallbackAcceptable(req func () error, fallback func (err error) error, acceptable Acceptable) error }
Breaker 接口定義了 6 個方法,提供了 2 中對熔斷器控制執(zhí)行的機制:
Allow 方法拋出了一個 Promise 句柄,允許用戶自行對熔斷指標(biāo)進行控制,采集指標(biāo)可以根據(jù)用戶需求選擇請求時延、錯誤碼等,且熔斷觸發(fā)后需要執(zhí)行的邏輯也有用戶自行控制。如 go-zero
中 rest 中熔斷中間件的用法:
func BreakerHandler(method, path string, metrics *stat.Metrics) func(http.Handler) http.Handler { brk := breaker.NewBreaker(breaker.WithName(strings.Join([]string{method, path}, breakerSeparator))) return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { promise, err := brk.Allow()// 熔斷執(zhí)行邏輯,用戶自行控制 if err != nil { metrics.AddDrop() logx.Errorf("[http] dropped, %s - %s - %s", r.RequestURI, httpx.GetRemoteAddr(r), r.UserAgent()) w.WriteHeader(http.StatusServiceUnavailable) return } cw := &response.WithCodeResponseWriter{Writer: w} defer func() {// 熔斷指標(biāo)采集,用戶自行控制 if cw.Code < http.StatusInternalServerError { promise.Accept() } else { promise.Reject(fmt.Sprintf("%d %s", cw.Code, http.StatusText(cw.Code))) } }() next.ServeHTTP(cw, r) }) } }
- DoXxx 方法則是另一個中熔斷器控制機制,其只能通過錯誤碼來進行指標(biāo)采集,當(dāng)然,用戶是可以根據(jù)錯誤碼來控制哪些錯誤碼是要加入到指標(biāo)里面的,除此外,
函數(shù)執(zhí)行體也是用用戶告知到熔斷器中,熔斷器會在熔斷未觸發(fā)的情況下執(zhí)行函數(shù)體,如果熔斷觸發(fā),則會執(zhí)行熔斷器中的邏輯。如返回熔斷錯誤。
DoXxx 方法在底層邏輯最終都是調(diào)用一個基礎(chǔ)方法,只是根據(jù)入?yún)⒌牟煌┞读瞬煌暮瘮?shù):
doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error
我們來看一下 doReq 方法的一個流程圖:
func (b *googleBreaker) doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error { // 判斷熔斷器是否開啟 if err := b.accept(); err != nil { if fallback != nil { return fallback(err) } return err } // 函數(shù)異常退出,上報失敗指標(biāo) defer func() { if e := recover(); e != nil { b.markFailure() panic(e) } }() // 執(zhí)行用戶函數(shù),并通過函數(shù)執(zhí)行返回的錯誤信息由用戶判斷是否為可接受的錯誤 // 上報指標(biāo) err := req() if acceptable(err) { b.markSuccess() } else { b.markFailure() } return err }
熔斷器狀態(tài)判斷
判斷熔斷器是否開啟是根據(jù)當(dāng)前窗口的歷史指標(biāo)來計算的,這里采用了 google SRE 里面的丟棄比例算法,當(dāng)丟棄比例為0時,則無需觸發(fā)熔斷,否則
隨機觸發(fā)熔斷機制。
func (b *googleBreaker) accept() error { accepts, total := b.history() weightedAccepts := b.k * float64(accepts) // https://landing.google.com/sre/sre-book/chapters/handling-overload/#eq2101 dropRatio := math.Max(0, (float64(total-protection)-weightedAccepts)/float64(total+1)) if dropRatio <= 0 { return nil } if b.proba.TrueOnProba(dropRatio) { return ErrServiceUnavailable } return nil } func (b *googleBreaker) history() (accepts, total int64) { b.stat.Reduce(func(b *collection.Bucket) { accepts += int64(b.Sum) total += b.Count }) return }
指標(biāo)上報
指標(biāo)上報是將用戶執(zhí)行后的請求錯誤返回給用戶,用戶可以根據(jù)錯誤碼指定改錯誤碼是否納入失敗指標(biāo)
// 標(biāo)記成功一次 func (b *googleBreaker) markSuccess() { b.stat.Add(1) } // 標(biāo)記失敗一次 func (b *googleBreaker) markFailure() { b.stat.Add(0) }
總結(jié)
熔斷器的實現(xiàn)起始就是一個指標(biāo)采集+指標(biāo)計算的過程,然后用戶可以根據(jù)指標(biāo)計算結(jié)果來決定是否觸發(fā)熔斷,其中難點還是在于指標(biāo)統(tǒng)計過程,這個過程是用滑動窗口來進行指標(biāo)采集的,關(guān)于滑動窗口算法這里就不做過多的介紹了。
以上就是go-zero組件Breaker的詳細內(nèi)容,更多關(guān)于go-zero 組件Breaker的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
golang中拿slice當(dāng)queue和拿list當(dāng)queue使用分析
這篇文章主要為大家介紹了golang?中拿slice當(dāng)queue和拿list當(dāng)queue使用分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08淺談用Go構(gòu)建不可變的數(shù)據(jù)結(jié)構(gòu)的方法
這篇文章主要介紹了用Go構(gòu)建不可變的數(shù)據(jù)結(jié)構(gòu)的方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09解決Golang小數(shù)float64在實際工程中加減乘除的精度問題
這篇文章主要介紹了解決Golang小數(shù)float64在實際工程中加減乘除的精度問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-03-03