golang?熔斷器的實現(xiàn)過程
熔斷器像是一個保險絲。當我們依賴的服務(wù)出現(xiàn)問題時,可以及時容錯。一方面可以減少依賴服務(wù)對自身訪問的依賴,防止出現(xiàn)雪崩效應(yīng);另一方面降低請求頻率以方便上游盡快恢復(fù)服務(wù)。
熔斷器的應(yīng)用也非常廣泛。除了在我們應(yīng)用中,為了請求服務(wù)時使用熔斷器外,在 web 網(wǎng)關(guān)、微服務(wù)中,也有非常廣泛的應(yīng)用。本文將從源碼角度學(xué)習(xí) sony 開源的一個熔斷器實現(xiàn)
github/sony/gobreaker
(代碼注釋可以從github/lpflpf/gobreaker
查看)
1.熔斷器的模式
gobreaker
是基于《微軟云設(shè)計模式》一書中的熔斷器模式的 Golang 實現(xiàn)。有 sony 公司開源,目前 star 數(shù)有 1.2K。使用人數(shù)較多。
下面是模式定義的一個狀態(tài)機:
熔斷器有三種狀態(tài),四種狀態(tài)轉(zhuǎn)移的情況:
- 熔斷器關(guān)閉狀態(tài),服務(wù)正常訪問
- 熔斷器開啟狀態(tài),服務(wù)異常
- 熔斷器半開狀態(tài),部分請求限流訪問
四種狀態(tài)轉(zhuǎn)移:
- 在熔斷器關(guān)閉狀態(tài)下,當失敗后并滿足一定條件后,將直接轉(zhuǎn)移為熔斷器開啟狀態(tài)。
- 在熔斷器開啟狀態(tài)下,如果過了規(guī)定的時間,將進入半開啟狀態(tài),驗證目前服務(wù)是否可用。
- 在熔斷器半開啟狀態(tài)下,如果出現(xiàn)失敗,則再次進入關(guān)閉狀態(tài)。
- 在熔斷器半開啟后,所有請求(有限額)都是成功的,則熔斷器關(guān)閉。所有請求將正常訪問。
2.gobreaker 的實現(xiàn)
gobreaker
是在上述狀態(tài)機的基礎(chǔ)上,實現(xiàn)的一個熔斷器。
2.1熔斷器的定義
type CircuitBreaker struct { ? ? name ? ? ? ? ?string ? ? maxRequests ? uint32 ?// 最大請求數(shù) (半開啟狀態(tài)會限流) ? ? interval ? ? ?time.Duration ? // 統(tǒng)計周期 ? ? timeout ? ? ? time.Duration ? // 進入熔斷后的超時時間 ? ? readyToTrip ? func(counts Counts) bool // 通過 Counts 判斷是否開啟熔斷。需要自定義 ? ? onStateChange func(name string, from State, to State) // 狀態(tài)修改時的鉤子函數(shù) ? ? mutex ? ? ?sync.Mutex // 互斥鎖,下面數(shù)據(jù)的更新都需要加鎖 ? ? state ? ? ?State ?// 記錄了當前的狀態(tài) ? ? generation uint64 // 標記屬于哪個周期 ? ? counts ? ? Counts // 計數(shù)器,統(tǒng)計了 成功、失敗、連續(xù)成功、連續(xù)失敗等,用于決策是否進入熔斷 ? ? expiry ? ? time.Time // 進入下個周期的時間 ? } ?
其中,如下參數(shù)是我們可以自定義的:
MaxRequests
:最大請求數(shù)。當在最大請求數(shù)下,均請求正常的情況下,會關(guān)閉熔斷器interval
:一個正常的統(tǒng)計周期。如果為 0,那每次都會將計數(shù)清零timeout
: 進入熔斷后,可以再次請求的時間readyToTrip
:判斷熔斷生效的鉤子函數(shù)onStateChagne
:狀態(tài)變更的鉤子函數(shù)
2.2請求的執(zhí)行
熔斷器的執(zhí)行操作,主要包括三個階段;①請求之前的判定;②服務(wù)的請求執(zhí)行;③請求后的狀態(tài)和計數(shù)的更新
// 熔斷器的調(diào)用 ? func (cb *CircuitBreaker) Execute(req func() (interface{}, error)) (interface{}, error) { ? ? // ①請求之前的判斷 ? ? generation, err := cb.beforeRequest() ? ? if err != nil { ? ? ? return nil, err ? ? } ? ? defer func() { ? ? ? e := recover() ? ? ? if e != nil { ? ? ? ? // ③ panic 的捕獲 ? ? ? ? cb.afterRequest(generation, false) ? ? ? ? panic(e) ? ? ? } ? ? }() ? ? // ② 請求和執(zhí)行 ? ? result, err := req() ? ? // ③ 更新計數(shù) ? ? cb.afterRequest(generation, err == nil) ? ? return result, err ? } ?
2.3請求之前的判定操作
請求之前,會判斷當前熔斷器的狀態(tài)。如果熔斷器以開啟,則不會繼續(xù)請求。如果熔斷器半開,并且已達到最大請求閾值,也不會繼續(xù)請求。
func (cb *CircuitBreaker) beforeRequest() (uint64, error) { ? ? cb.mutex.Lock() ? ? defer cb.mutex.Unlock() ? ? now := time.Now() ? ? state, generation := cb.currentState(now) ? ? if state == StateOpen { // 熔斷器開啟,直接返回 ? ? ? return generation, ErrOpenState ? ? } else if state == StateHalfOpen && cb.counts.Requests >= cb.maxRequests { // 如果是半打開的狀態(tài),并且請求次數(shù)過多了,則直接返回 ? ? ? return generation, ErrTooManyRequests ? ? } ? ? cb.counts.onRequest() ? ? return generation, nil ? } ?
其中當前狀態(tài)的計算,是依據(jù)當前狀態(tài)來的。如果當前狀態(tài)為已開啟,則判斷是否已經(jīng)超時,超時就可以變更狀態(tài)到半開;如果當前狀態(tài)為關(guān)閉狀態(tài),則通過周期判斷是否進入下一個周期。
func (cb *CircuitBreaker) currentState(now time.Time) (State, uint64) { ? ? switch cb.state { ? ? case StateClosed: ? ? ? if !cb.expiry.IsZero() && cb.expiry.Before(now) { // 是否需要進入下一個計數(shù)周期 ? ? ? ? cb.toNewGeneration(now) ? ? ? } ? ? case StateOpen: ? ? ? if cb.expiry.Before(now) { ? ? ? ? // 熔斷器由開啟變更為半開 ? ? ? ? cb.setState(StateHalfOpen, now) ? ? ? } ? ? } ? ? return cb.state, cb.generation ? } ?
周期長度的設(shè)定,也是以據(jù)當前狀態(tài)來的。如果當前正常(熔斷器關(guān)閉),則設(shè)置為一個 interval 的周期;如果當前熔斷器是開啟狀態(tài),則設(shè)置為超時時間(超時后,才能變更為半開狀態(tài))。
2.4請求之后的處理操作
每次請求之后,會通過請求結(jié)果是否成功,對熔斷器做計數(shù)。
func (cb *CircuitBreaker) afterRequest(before uint64, success bool) { ? ? cb.mutex.Lock() ? ? defer cb.mutex.Unlock() ? ? now := time.Now() ? ? // 如果不在一個周期,就不再計數(shù) ? ? state, generation := cb.currentState(now) ? ? if generation != before { ? ? ? return ? ? } ? ? if success { ? ? ? cb.onSuccess(state, now) ? ? } else { ? ? ? cb.onFailure(state, now) ? ? } ? } ?
如果在半開的狀態(tài)下:
如果請求成功,則會判斷當前連續(xù)成功的請求數(shù) 大于等于 maxRequests, 則可以把狀態(tài)由半開狀態(tài)轉(zhuǎn)移為關(guān)閉狀態(tài)
如果在半開狀態(tài)下,請求失敗,則會直接將半開狀態(tài)轉(zhuǎn)移為開啟狀態(tài)
如果在關(guān)閉狀態(tài)下:
如果請求成功,則計數(shù)更新
如果請求失敗,則調(diào)用 readyToTrip
判斷是否需要將狀態(tài)關(guān)閉狀態(tài)轉(zhuǎn)移為開啟狀態(tài)
總結(jié):
- 于頻繁請求一些遠程或者第三方的不可靠的服務(wù),存在失敗的概率還是非常大的。使用熔斷器的好處就是可以是我們自身的服務(wù)不被這些不可靠的服務(wù)拖垮,造成雪崩。
- 由于熔斷器里面,不僅會維護不少的統(tǒng)計數(shù)據(jù),還有互斥鎖做資源隔離,成本也會不少。
- 在半開狀態(tài)下,可能出現(xiàn)請求過多的情況。這是由于半開狀態(tài)下,連續(xù)請求成功的數(shù)量未達到最大請求值。所以,熔斷器對于請求時間過長(但是比較頻繁)的服務(wù)可能會造成大量的 too many requests 錯誤
到此這篇關(guān)于golang 熔斷器的實現(xiàn)過程的文章就介紹到這了,更多相關(guān)golang 熔斷器的實現(xiàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang中g(shù)in框架綁定解析json數(shù)據(jù)的兩種方法
本文介紹 Golang 的 gin 框架接收json數(shù)據(jù)并解析的2種方法,文中通過代碼示例介紹的非常詳細,對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2023-12-12