Go 項目中使用熔斷技術提高系統容錯性。本文介紹了 go 熔斷器和其使用。
熔斷器像是一個保險絲。當我們依賴的服務出現問題時,可以及時容錯。一方面可以減少依賴服務對自身訪問的依賴,防止出現雪崩效應;另一方面降低請求頻率以方便上游盡快恢復服務。
熔斷器的應用也非常廣泛。除了在我們應用中,為了請求服務時使用熔斷器外,在 web 網關、微服務中,也有非常廣泛的應用。本文將從源碼角度學習 sony 開源的一個熔斷器實現 github/sony/gobreaker
。(代碼注釋可以從github/lpflpf/gobreaker
查看)
熔斷器的模式
gobreaker 是基于《微軟云設計模式》一書中的熔斷器模式的 Golang 實現。有 sony 公司開源,目前 star 數有 1.2K。使用人數較多。
下面是模式定義的一個狀態機:
熔斷器有三種狀態,四種狀態轉移的情況:
三種狀態:
-
熔斷器關閉狀態,服務正常訪問
-
熔斷器開啟狀態,服務異常
-
熔斷器半開狀態,部分請求限流訪問
四種狀態轉移:
-
在熔斷器關閉狀態下,當失敗后并滿足一定條件后,將直接轉移為熔斷器開啟狀態。
-
在熔斷器開啟狀態下,如果過了規定的時間,將進入半開啟狀態,驗證目前服務是否可用。
-
在熔斷器半開啟狀態下,如果出現失敗,則再次進入關閉狀態。
-
在熔斷器半開啟后,所有請求(有限額)都是成功的,則熔斷器關閉。所有請求將正常訪問。
gobreaker 的實現
gobreaker 是在上述狀態機的基礎上,實現的一個熔斷器。
熔斷器的定義
typeCircuitBreakerstruct{
namestring
maxRequestsuint32//最大請求數(半開啟狀態會限流)
intervaltime.Duration//統計周期
timeouttime.Duration//進入熔斷后的超時時間
readyToTripfunc(countsCounts)bool//通過Counts判斷是否開啟熔斷。需要自定義
onStateChangefunc(namestring,fromState,toState)//狀態修改時的鉤子函數
mutexsync.Mutex//互斥鎖,下面數據的更新都需要加鎖
stateState//記錄了當前的狀態
generationuint64//標記屬于哪個周期
countsCounts//計數器,統計了成功、失敗、連續成功、連續失敗等,用于決策是否進入熔斷
expirytime.Time//進入下個周期的時間
}
其中,如下參數是我們可以自定義的:
-
MaxRequests:最大請求數。當在最大請求數下,均請求正常的情況下,會關閉熔斷器
-
interval:一個正常的統計周期。如果為 0,那每次都會將計數清零
-
timeout: 進入熔斷后,可以再次請求的時間
-
readyToTrip:判斷熔斷生效的鉤子函數
-
onStateChagne:狀態變更的鉤子函數
請求的執行
熔斷器的執行操作,主要包括三個階段;①請求之前的判定;②服務的請求執行;③請求后的狀態和計數的更新
//熔斷器的調用
func(cb*CircuitBreaker)Execute(reqfunc()(interface{},error))(interface{},error){
//①請求之前的判斷
generation,err:=cb.beforeRequest()
iferr!=nil{
returnnil,err
}
deferfunc(){
e:=recover()
ife!=nil{
//③panic的捕獲
cb.afterRequest(generation,false)
panic(e)
}
}()
//②請求和執行
result,err:=req()
//③更新計數
cb.afterRequest(generation,err==nil)
returnresult,err
}
請求之前的判定操作
請求之前,會判斷當前熔斷器的狀態。如果熔斷器以開啟,則不會繼續請求。如果熔斷器半開,并且已達到最大請求閾值,也不會繼續請求。
func(cb*CircuitBreaker)beforeRequest()(uint64,error){
cb.mutex.Lock()
defercb.mutex.Unlock()
now:=time.Now()
state,generation:=cb.currentState(now)
ifstate==StateOpen{//熔斷器開啟,直接返回
returngeneration,ErrOpenState
}elseifstate==StateHalfOpen&&cb.counts.Requests>=cb.maxRequests{//如果是半打開的狀態,并且請求次數過多了,則直接返回
returngeneration,ErrTooManyRequests
}
cb.counts.onRequest()
returngeneration,nil
}
其中當前狀態的計算,是依據當前狀態來的。如果當前狀態為已開啟,則判斷是否已經超時,超時就可以變更狀態到半開;如果當前狀態為關閉狀態,則通過周期判斷是否進入下一個周期。
func(cb*CircuitBreaker)currentState(nowtime.Time)(State,uint64){
switchcb.state{
caseStateClosed:
if!cb.expiry.IsZero()&&cb.expiry.Before(now){//是否需要進入下一個計數周期
cb.toNewGeneration(now)
}
caseStateOpen:
ifcb.expiry.Before(now){
//熔斷器由開啟變更為半開
cb.setState(StateHalfOpen,now)
}
}
returncb.state,cb.generation
}
周期長度的設定,也是以據當前狀態來的。如果當前正常(熔斷器關閉),則設置為一個 interval 的周期;如果當前熔斷器是開啟狀態,則設置為超時時間(超時后,才能變更為半開狀態)。
請求之后的處理操作
每次請求之后,會通過請求結果是否成功,對熔斷器做計數。
func(cb*CircuitBreaker)afterRequest(beforeuint64,successbool){
cb.mutex.Lock()
defercb.mutex.Unlock()
now:=time.Now()
//如果不在一個周期,就不再計數
state,generation:=cb.currentState(now)
ifgeneration!=before{
return
}
ifsuccess{
cb.onSuccess(state,now)
}else{
cb.onFailure(state,now)
}
}
如果在半開的狀態下:
-
如果請求成功,則會判斷當前連續成功的請求數 大于等于 maxRequests, 則可以把狀態由半開狀態轉移為關閉狀態
-
如果在半開狀態下,請求失敗,則會直接將半開狀態轉移為開啟狀態
如果在關閉狀態下:
-
如果請求成功,則計數更新
-
如果請求失敗,則調用 readyToTrip 判斷是否需要將狀態關閉狀態轉移為開啟狀態
總結
-
對于頻繁請求一些遠程或者第三方的不可靠的服務,存在失敗的概率還是非常大的。使用熔斷器的好處就是可以是我們自身的服務不被這些不可靠的服務拖垮,造成雪崩。
-
由于熔斷器里面,不僅會維護不少的統計數據,還有互斥鎖做資源隔離,成本也會不少。
-
在半開狀態下,可能出現請求過多的情況。這是由于半開狀態下,連續請求成功的數量未達到最大請求值。所以,熔斷器對于請求時間過長(但是比較頻繁)的服務可能會造成大量的
too many requests
錯誤
轉自:segmentfault.com/a/1190000023033343
-
數據
+關注
關注
8文章
7117瀏覽量
89339 -
熔斷器
+關注
關注
6文章
476瀏覽量
31639 -
go語言
+關注
關注
1文章
158瀏覽量
9063
原文標題:Golang 熔斷器的實現
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論