斷路器背景
微服務(wù)連鎖故障場(chǎng)景
在分布式環(huán)境中克懊,各個(gè)微服務(wù)相互調(diào)用秸妥,當(dāng)某些情況下,比如后端中間件服務(wù)故障榆芦、第三方服務(wù)中斷導(dǎo)致某個(gè)服務(wù)無(wú)限期不可用敦姻,短時(shí)間無(wú)法恢復(fù),則可能會(huì)導(dǎo)致連鎖故障歧杏,最終影響壓垮整個(gè)業(yè)務(wù)集群
斷路器與重試
斷路器模式不同于重試模式,重試模式是使應(yīng)用程序可以重試操作以期望它會(huì)成功迷守,而斷路器模式是防止應(yīng)用程序執(zhí)行一個(gè)可能失敗的操作犬绒,減少執(zhí)行可能失敗操作的CPU、內(nèi)存兑凿、線程等資源的浪費(fèi)凯力,從而保證服務(wù)的整體可用
斷路器設(shè)計(jì)解析
基于代理模式的斷路器
斷路器相當(dāng)于一個(gè)請(qǐng)求操作執(zhí)行的代理茵瘾,托管請(qǐng)求操作的執(zhí)行
實(shí)現(xiàn)原理流程:
- 攔截服務(wù)執(zhí)行的請(qǐng)求,通過(guò)當(dāng)前狀態(tài)決定是否直接返回咐鹤,如果否則執(zhí)行后續(xù)操作
- 嘗試執(zhí)行操作拗秘,并獲取返回結(jié)果
- 根據(jù)返回結(jié)果和當(dāng)前統(tǒng)計(jì)信息,決定當(dāng)前斷路器的狀態(tài)祈惶,修改狀態(tài)
- 返回執(zhí)行結(jié)果
斷路器狀態(tài)機(jī)
斷路器狀態(tài)機(jī)實(shí)現(xiàn)上有三種狀態(tài):Closed(斷路器關(guān)閉)雕旨、Open(開(kāi)放)、HalfOpen(半開(kāi)放)
狀態(tài) | 說(shuō)明 | 備注 |
---|---|---|
Closed | 關(guān)閉 | 斷路器關(guān)閉正常執(zhí)行操作 |
Open | 打開(kāi) | 斷路器開(kāi)放,所有請(qǐng)求直接返回錯(cuò)誤捧请,不執(zhí)行任何請(qǐng)求 |
HalfOpen | 半開(kāi)放 | 允許有限數(shù)量的請(qǐng)求通過(guò),如果執(zhí)行成功凡涩,恢復(fù)到關(guān)閉狀態(tài),如果仍然失敗疹蛉,則恢復(fù)到開(kāi)放活箕,然后重新啟動(dòng)超時(shí)定時(shí)器 |
斷路器實(shí)現(xiàn)
實(shí)現(xiàn)原理圖解
斷路器實(shí)現(xiàn)實(shí)現(xiàn)主要分為三部分:狀態(tài)統(tǒng)計(jì)、狀態(tài)轉(zhuǎn)移可款、請(qǐng)求執(zhí)行
狀態(tài)統(tǒng)計(jì):統(tǒng)計(jì)已經(jīng)執(zhí)行的請(qǐng)求的成功失敗的數(shù)量育韩,以確定是否需要進(jìn)行狀態(tài)轉(zhuǎn)移
狀態(tài)轉(zhuǎn)移:根據(jù)當(dāng)前統(tǒng)計(jì)信息和當(dāng)前狀態(tài)來(lái)進(jìn)行目標(biāo)狀態(tài)的確定及轉(zhuǎn)移操作
請(qǐng)求執(zhí)行:代理前端任務(wù)的執(zhí)行,如果當(dāng)前狀態(tài)不需要進(jìn)行嘗試執(zhí)行闺鲸,就直接返回錯(cuò)誤筋讨,避免資源浪費(fèi)
Golang里面已經(jīng)有開(kāi)源的實(shí)現(xiàn),https://github.com/sony/gobreaker/blob/, 接下來(lái)救市剖析它的實(shí)現(xiàn)
狀態(tài)統(tǒng)計(jì)-計(jì)數(shù)器Counts
Counts就是一個(gè)計(jì)數(shù)器翠拣,記錄當(dāng)前請(qǐng)求成功和失敗的數(shù)量
type Counts struct {
Requests uint32 // 請(qǐng)求數(shù)
TotalSuccesses uint32 // 成功
TotalFailures uint32 // 失敗
ConsecutiveSuccesses uint32 // 連續(xù)成功
ConsecutiveFailures uint32 // 連續(xù)失敗
}
計(jì)數(shù)器完成對(duì)應(yīng)請(qǐng)求狀態(tài)的次數(shù)版仔,為后續(xù)狀態(tài)轉(zhuǎn)移提供數(shù)據(jù), Counts提供了onRequest、onSuccess误墓、onFailure蛮粮、clear幾個(gè)輔助接口用于實(shí)現(xiàn)對(duì)應(yīng)請(qǐng)求狀態(tài)的操作,感興趣可以看下
狀態(tài)機(jī)- CircuitBreaker
type CircuitBreaker struct {
name string
// maxRequests限制half-open狀態(tài)下最大的請(qǐng)求數(shù)谜慌,避免海量請(qǐng)求將在恢復(fù)過(guò)程中的服務(wù)再次失敗
maxRequests uint32
// interval用于在closed狀態(tài)下然想,斷路器多久清除一次Counts信息,如果設(shè)置為0則在closed狀態(tài)下不會(huì)清除Counts
interval time.Duration
// timeout進(jìn)入open狀態(tài)下欣范,多長(zhǎng)時(shí)間切換到half-open狀態(tài)变泄,默認(rèn)60s
timeout time.Duration
// readyToTrip熔斷條件,當(dāng)執(zhí)行失敗后恼琼,會(huì)根據(jù)readyToTrip決定是否進(jìn)入Open狀態(tài)
readyToTrip func(counts Counts) bool
// onStateChange斷路器狀態(tài)變更回調(diào)函數(shù)
onStateChange func(name string, from State, to State)
mutex sync.Mutex
//. state 斷路器狀態(tài)
state State
// generation 是一個(gè)遞增值妨蛹,相當(dāng)于當(dāng)前斷路器狀態(tài)切換的次數(shù), 為了避免狀態(tài)切換后,未完成請(qǐng)求對(duì)新?tīng)顟B(tài)的統(tǒng)計(jì)的影響翠忠,如果發(fā)現(xiàn)一個(gè)請(qǐng)求的generation同當(dāng)前的generation不同拂蝎,則不會(huì)進(jìn)行統(tǒng)計(jì)計(jì)數(shù)
generation uint64
// Counts 統(tǒng)計(jì)
counts Counts
// expiry 超時(shí)過(guò)期用于open狀態(tài)到half-open狀態(tài)的切換弃甥,當(dāng)超時(shí)后颤难,會(huì)從open狀態(tài)切換到half-open狀態(tài)
expiry time.Time
}
核心流程
CircuitBreaker.Execute
請(qǐng)求執(zhí)行神年,對(duì)外開(kāi)放的請(qǐng)求執(zhí)行接口
func (cb *CircuitBreaker) Execute(req func() (interface{}, error)) (interface{}, error) {
// 執(zhí)行請(qǐng)求鉤子,會(huì)根據(jù)當(dāng)前狀態(tài)行嗤,來(lái)返回當(dāng)前的generation和err(如果位于open和half-open則不為nil), 通過(guò)err來(lái)進(jìn)行判斷是否直接返回
generation, err := cb.beforeRequest()
if err != nil {
return nil, err
}
// 捕獲panic已日,避免應(yīng)用函數(shù)錯(cuò)誤造成斷路器panic
defer func() {
e := recover()
if e != nil {
cb.afterRequest(generation, false)
panic(e)
}
}()
// 執(zhí)行請(qǐng)求
result, err := req()
// 根據(jù)結(jié)果來(lái)進(jìn)行對(duì)應(yīng)狀態(tài)的統(tǒng)計(jì), 同時(shí)傳遞generation
cb.afterRequest(generation, err == nil)
return result, err
}
CircuitBreaker.beforeRequest
func (cb *CircuitBreaker) beforeRequest() (uint64, error) {
cb.mutex.Lock()
defer cb.mutex.Unlock()
// 獲取當(dāng)前的狀態(tài)
now := time.Now()
state, generation := cb.currentState(now)
// open和half-open狀態(tài)則直接返回
if state == StateOpen {
return generation, ErrOpenState
} else if state == StateHalfOpen && cb.counts.Requests >= cb.maxRequests {
// 避免海量請(qǐng)求對(duì)處于恢復(fù)服務(wù)的影響,這里有一個(gè)限流的操作栅屏,避免請(qǐng)求數(shù)超過(guò)最大請(qǐng)求數(shù)
return generation, ErrTooManyRequests
}
// 統(tǒng)計(jì)狀態(tài)
cb.counts.onRequest()
return generation, nil
}
CircuitBreaker.afterRequest
func (cb *CircuitBreaker) afterRequest(before uint64, success bool) {
cb.mutex.Lock()
defer cb.mutex.Unlock()
// 重新獲取狀態(tài)
now := time.Now()
state, generation := cb.currentState(now)
// 如果前后狀態(tài)不一致飘千,則不計(jì)數(shù)
if generation != before {
return
}
// 根據(jù)狀態(tài)計(jì)數(shù)
if success {
cb.onSuccess(state, now)
} else {
cb.onFailure(state, now)
}
}
CircuitBreaker.currentState
func (cb *CircuitBreaker) currentState(now time.Time) (State, uint64) {
switch cb.state {
case StateClosed:
// 如果當(dāng)前當(dāng)前是closed狀態(tài),并且有設(shè)置expiry,則遞增Generation到新一輪統(tǒng)計(jì)計(jì)數(shù)
if !cb.expiry.IsZero() && cb.expiry.Before(now) {
cb.toNewGeneration(now)
}
case StateOpen:
// 如果是Open狀態(tài)既琴,并且超時(shí)占婉,則嘗試到半打開(kāi)狀態(tài)
if cb.expiry.Before(now) {
cb.setState(StateHalfOpen, now)
}
}
return cb.state, cb.generation
}
CircuitBreaker.toNewgeneration
func (cb *CircuitBreaker) toNewGeneration(now time.Time) {
// 遞增generation, 清除狀態(tài)
cb.generation++
cb.counts.clear()
// 設(shè)置超時(shí)時(shí)間
var zero time.Time
switch cb.state {
case StateClosed:
if cb.interval == 0 {
cb.expiry = zero
} else {
cb.expiry = now.Add(cb.interval)
}
case StateOpen:
cb.expiry = now.Add(cb.timeout)
default: // StateHalfOpen
cb.expiry = zero
}
}
總結(jié)
斷路器黃金鏈路
- beforeRequest :完成當(dāng)前請(qǐng)求是否可以執(zhí)行請(qǐng)求,狀態(tài)超時(shí)切換甫恩,同時(shí)返回當(dāng)前的genenration
- req: 執(zhí)行請(qǐng)求
- afterRequest: 完成請(qǐng)求狀態(tài)統(tǒng)計(jì)逆济,決定狀態(tài)切換
斷路器的優(yōu)缺點(diǎn)
斷路器比較適合針對(duì)遠(yuǎn)程服務(wù)或者第三方服務(wù)的調(diào)用,如果該操作極有可能會(huì)失敗磺箕,則斷路器可以盡可能的減小失敗對(duì)應(yīng)用的影響奖慌,避免資源浪費(fèi)
但缺點(diǎn)也顯而易見(jiàn),斷路器本身相當(dāng)于一層代理松靡,在應(yīng)用程序執(zhí)行進(jìn)行統(tǒng)計(jì)和控制简僧,本身就有一定的資源消耗,同時(shí)內(nèi)部基于synx.Mutex鎖來(lái)實(shí)現(xiàn)雕欺,高并發(fā)下肯定會(huì)有鎖爭(zhēng)用問(wèn)題岛马,可能需要根據(jù)業(yè)務(wù)來(lái)使用多個(gè)斷路器,來(lái)分散這種鎖爭(zhēng)用屠列,同時(shí)應(yīng)該避免在斷路器req函數(shù)內(nèi)啦逆,去執(zhí)行重試和過(guò)長(zhǎng)時(shí)間的超時(shí)等待,因?yàn)閿嗦菲骱诵氖强焖偈?/p>
更多文章可以訪問(wèn)http://www.sreguide.com/