微服務(wù)的斷路器實(shí)現(xiàn)圖解Golang通用版

斷路器背景

微服務(wù)連鎖故障場(chǎng)景

image

在分布式環(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ì)解析

基于代理模式的斷路器

image

斷路器相當(dāng)于一個(gè)請(qǐng)求操作執(zhí)行的代理茵瘾,托管請(qǐng)求操作的執(zhí)行

實(shí)現(xiàn)原理流程:

  1. 攔截服務(wù)執(zhí)行的請(qǐng)求,通過(guò)當(dāng)前狀態(tài)決定是否直接返回咐鹤,如果否則執(zhí)行后續(xù)操作
  2. 嘗試執(zhí)行操作拗秘,并獲取返回結(jié)果
  3. 根據(jù)返回結(jié)果和當(dāng)前統(tǒng)計(jì)信息,決定當(dāng)前斷路器的狀態(tài)祈惶,修改狀態(tài)
  4. 返回執(zhí)行結(jié)果

斷路器狀態(tài)機(jī)

image

斷路器狀態(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)原理圖解

image

斷路器實(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é)

斷路器黃金鏈路

image
  • 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/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末笛洛,一起剝皮案震驚了整個(gè)濱河市夏志,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌苛让,老刑警劉巖沟蔑,帶你破解...
    沈念sama閱讀 222,464評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異狱杰,居然都是意外死亡瘦材,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)仿畸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)食棕,“玉大人,你說(shuō)我怎么就攤上這事⌒洌” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,078評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵甥捺,是天一觀的道長(zhǎng)抢蚀。 經(jīng)常有香客問(wèn)我,道長(zhǎng)镰禾,這世上最難降的妖魔是什么皿曲? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,979評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮吴侦,結(jié)果婚禮上屋休,老公的妹妹穿的比我還像新娘。我一直安慰自己备韧,他們只是感情好劫樟,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,001評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著织堂,像睡著了一般叠艳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上易阳,一...
    開(kāi)封第一講書(shū)人閱讀 52,584評(píng)論 1 312
  • 那天附较,我揣著相機(jī)與錄音,去河邊找鬼潦俺。 笑死拒课,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的事示。 我是一名探鬼主播早像,決...
    沈念sama閱讀 41,085評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼很魂!你這毒婦竟也來(lái)了扎酷?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,023評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤遏匆,失蹤者是張志新(化名)和其女友劉穎法挨,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體幅聘,經(jīng)...
    沈念sama閱讀 46,555評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凡纳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,626評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了帝蒿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荐糜。...
    茶點(diǎn)故事閱讀 40,769評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出暴氏,到底是詐尸還是另有隱情延塑,我是刑警寧澤,帶...
    沈念sama閱讀 36,439評(píng)論 5 351
  • 正文 年R本政府宣布答渔,位于F島的核電站关带,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏沼撕。R本人自食惡果不足惜宋雏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,115評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望务豺。 院中可真熱鬧磨总,春花似錦、人聲如沸笼沥。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,601評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)敬拓。三九已至邻薯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間乘凸,已是汗流浹背厕诡。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,702評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留营勤,地道東北人灵嫌。 一個(gè)月前我還...
    沈念sama閱讀 49,191評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像葛作,于是被迫代替她去往敵國(guó)和親寿羞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,781評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • 專業(yè)考題類型管理運(yùn)行工作負(fù)責(zé)人一般作業(yè)考題內(nèi)容選項(xiàng)A選項(xiàng)B選項(xiàng)C選項(xiàng)D選項(xiàng)E選項(xiàng)F正確答案 變電單選GYSZ本規(guī)程...
    小白兔去釣魚(yú)閱讀 9,013評(píng)論 0 13
  • ORA-00001: 違反唯一約束條件 (.) 錯(cuò)誤說(shuō)明:當(dāng)在唯一索引所對(duì)應(yīng)的列上鍵入重復(fù)值時(shí)赂蠢,會(huì)觸發(fā)此異常绪穆。 O...
    我想起個(gè)好名字閱讀 5,341評(píng)論 0 9
  • 不可否認(rèn),在過(guò)去幾年中虱岂,像Docker和Kubernetes這樣的技術(shù)玖院,徹底改變了我們對(duì)軟件開(kāi)發(fā)和部署的方式。 盡...
    water_lang閱讀 2,466評(píng)論 0 1
  • 斷路器模式 當(dāng)連接到遠(yuǎn)程服務(wù)或資源到時(shí)候第岖,處理那些需要一段時(shí)間才能修復(fù)的系統(tǒng)缺陷难菌。這能優(yōu)化應(yīng)用對(duì)穩(wěn)定性和可靠性。 ...
    汀三丁閱讀 487評(píng)論 1 2
  • 當(dāng)一個(gè)單體應(yīng)用改造成多個(gè)微服務(wù)之后遇绞,在請(qǐng)求調(diào)用過(guò)程中往往會(huì)出現(xiàn)更多的問(wèn)題,通信過(guò)程中的每一個(gè)環(huán)節(jié)都可能出現(xiàn)問(wèn)題燎窘。而...
    fredal閱讀 1,111評(píng)論 0 1