Golang 熔斷器的實現(xiàn)

Go 項目中使用熔斷技術提高系統(tǒng)容錯性。本文介紹了 go 熔斷器和其使用。

熔斷器像是一個保險絲。當我們依賴的服務出現(xiàn)問題時,可以及時容錯。一方面可以減少依賴服務對自身訪問的依賴,防止出現(xiàn)雪崩效應;另一方面降低請求頻率以方便上游盡快恢復服務。
熔斷器的應用也非常廣泛。除了在我們應用中,為了請求服務時使用熔斷器外,在 web 網關、微服務中,也有非常廣泛的應用。本文將從源碼角度學習 sony 開源的一個熔斷器實現(xiàn) github/sony/gobreaker。(代碼注釋可以從github/lpflpf/gobreaker 查看)
熔斷器的模式
gobreaker 是基于《微軟云設計模式》一書中的熔斷器模式的 Golang 實現(xiàn)。有 sony 公司開源,目前 star 數(shù)有 1.2K。使用人數(shù)較多。
下面是模式定義的一個狀態(tài)機:

熔斷器有三種狀態(tài),四種狀態(tài)轉移的情況:
三種狀態(tài):
熔斷器關閉狀態(tài),服務正常訪問
熔斷器開啟狀態(tài),服務異常
熔斷器半開狀態(tài),部分請求限流訪問
四種狀態(tài)轉移:
在熔斷器關閉狀態(tài)下,當失敗后并滿足一定條件后,將直接轉移為熔斷器開啟狀態(tài)。
在熔斷器開啟狀態(tài)下,如果過了規(guī)定的時間,將進入半開啟狀態(tài),驗證目前服務是否可用。
在熔斷器半開啟狀態(tài)下,如果出現(xiàn)失敗,則再次進入關閉狀態(tài)。
在熔斷器半開啟后,所有請求(有限額)都是成功的,則熔斷器關閉。所有請求將正常訪問。
gobreaker 的實現(xiàn)
gobreaker 是在上述狀態(tài)機的基礎上,實現(xiàn)的一個熔斷器。
熔斷器的定義
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ù)據的更新都需要加鎖??
??state??????State??//?記錄了當前的狀態(tài)??
??generation?uint64?//?標記屬于哪個周期??
??counts?????Counts?//?計數(shù)器,統(tǒng)計了?成功、失敗、連續(xù)成功、連續(xù)失敗等,用于決策是否進入熔斷??
??expiry?????time.Time?//?進入下個周期的時間??
}??
其中,如下參數(shù)是我們可以自定義的:
MaxRequests:最大請求數(shù)。當在最大請求數(shù)下,均請求正常的情況下,會關閉熔斷器
interval:一個正常的統(tǒng)計周期。如果為 0,那每次都會將計數(shù)清零
timeout: 進入熔斷后,可以再次請求的時間
readyToTrip:判斷熔斷生效的鉤子函數(shù)
onStateChagne:狀態(tài)變更的鉤子函數(shù)
請求的執(zhí)行
熔斷器的執(zhí)行操作,主要包括三個階段;①請求之前的判定;②服務的請求執(zhí)行;③請求后的狀態(tài)和計數(shù)的更新
//?熔斷器的調用??
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??
}??
請求之前的判定操作
請求之前,會判斷當前熔斷器的狀態(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)的計算,是依據當前狀態(tài)來的。如果當前狀態(tài)為已開啟,則判斷是否已經超時,超時就可以變更狀態(tài)到半開;如果當前狀態(tài)為關閉狀態(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??
}??
周期長度的設定,也是以據當前狀態(tài)來的。如果當前正常(熔斷器關閉),則設置為一個 interval 的周期;如果當前熔斷器是開啟狀態(tài),則設置為超時時間(超時后,才能變更為半開狀態(tài))。
請求之后的處理操作
每次請求之后,會通過請求結果是否成功,對熔斷器做計數(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)轉移為關閉狀態(tài)
如果在半開狀態(tài)下,請求失敗,則會直接將半開狀態(tài)轉移為開啟狀態(tài)
如果在關閉狀態(tài)下:
如果請求成功,則計數(shù)更新
如果請求失敗,則調用 readyToTrip 判斷是否需要將狀態(tài)關閉狀態(tài)轉移為開啟狀態(tài)
總結
對于頻繁請求一些遠程或者第三方的不可靠的服務,存在失敗的概率還是非常大的。使用熔斷器的好處就是可以是我們自身的服務不被這些不可靠的服務拖垮,造成雪崩。
由于熔斷器里面,不僅會維護不少的統(tǒng)計數(shù)據,還有互斥鎖做資源隔離,成本也會不少。
在半開狀態(tài)下,可能出現(xiàn)請求過多的情況。這是由于半開狀態(tài)下,連續(xù)請求成功的數(shù)量未達到最大請求值。所以,熔斷器對于請求時間過長(但是比較頻繁)的服務可能會造成大量的
too many requests錯誤
轉自:segmentfault.com/a/1190000023033343
文章轉載:Go開發(fā)大全
(版權歸原作者所有,侵刪)
