熔斷原理與實現(xiàn)Golang版

在微服務中服務間依賴非常常見,比如評論服務依賴審核服務而審核服務又依賴反垃圾服務斤程,當評論服務調(diào)用審核服務時融痛,審核服務又調(diào)用反垃圾服務肠缔,而這時反垃圾服務超時了撕攒,由于審核服務依賴反垃圾服務咬像,反垃圾服務超時導致審核服務邏輯一直等待,而這個時候評論服務又在一直調(diào)用審核服務疚脐,審核服務就有可能因為堆積了大量請求而導致服務宕機

call_chain.png

由此可見亿柑,在整個調(diào)用鏈中,中間的某一個環(huán)節(jié)出現(xiàn)異常就會引起上游調(diào)用服務出現(xiàn)一些列的問題棍弄,甚至導致整個調(diào)用鏈的服務都宕機望薄,這是非常可怕的照卦。因此一個服務作為調(diào)用方調(diào)用另一個服務時式矫,為了防止被調(diào)用服務出現(xiàn)問題進而導致調(diào)用服務出現(xiàn)問題,所以調(diào)用服務需要進行自我保護役耕,而保護的常用手段就是熔斷

熔斷器原理

熔斷機制其實是參考了我們?nèi)粘I钪械谋kU絲的保護機制采转,當電路超負荷運行時,保險絲會自動的斷開,從而保證電路中的電器不受損害故慈。而服務治理中的熔斷機制板熊,指的是在發(fā)起服務調(diào)用的時候,如果被調(diào)用方返回的錯誤率超過一定的閾值察绷,那么后續(xù)的請求將不會真正發(fā)起請求干签,而是在調(diào)用方直接返回錯誤

在這種模式下,服務調(diào)用方為每一個調(diào)用服務(調(diào)用路徑)維護一個狀態(tài)機拆撼,在這個狀態(tài)機中有三個狀態(tài):

  • 關閉(Closed):在這種狀態(tài)下容劳,我們需要一個計數(shù)器來記錄調(diào)用失敗的次數(shù)和總的請求次數(shù),如果在某個時間窗口內(nèi)闸度,失敗的失敗率達到預設的閾值竭贩,則切換到斷開狀態(tài),此時開啟一個超時時間莺禁,當?shù)竭_該時間則切換到半關閉狀態(tài)留量,該超時時間是給了系統(tǒng)一次機會來修正導致調(diào)用失敗的錯誤,以回到正常的工作狀態(tài)哟冬。在關閉狀態(tài)下楼熄,調(diào)用錯誤是基于時間的,在特定的時間間隔內(nèi)會重置浩峡,這能夠防止偶然錯誤導致熔斷器進去斷開狀態(tài)
  • 打開(Open):在該狀態(tài)下可岂,發(fā)起請求時會立即返回錯誤,一般會啟動一個超時計時器红符,當計時器超時后青柄,狀態(tài)切換到半打開狀態(tài),也可以設置一個定時器预侯,定期的探測服務是否恢復
  • 半打開(Half-Open):在該狀態(tài)下,允許應用程序一定數(shù)量的請求發(fā)往被調(diào)用服務峰锁,如果這些調(diào)用正常萎馅,那么可以認為被調(diào)用服務已經(jīng)恢復正常,此時熔斷器切換到關閉狀態(tài)虹蒋,同時需要重置計數(shù)糜芳。如果這部分仍有調(diào)用失敗的情況,則認為被調(diào)用方仍然沒有恢復魄衅,熔斷器會切換到關閉狀態(tài)峭竣,然后重置計數(shù)器,半打開狀態(tài)能夠有效防止正在恢復中的服務被突然大量請求再次打垮
breaker_state.png

服務治理中引入熔斷機制晃虫,使得系統(tǒng)更加穩(wěn)定和有彈性皆撩,在系統(tǒng)從錯誤中恢復的時候提供穩(wěn)定性,并且減少了錯誤對系統(tǒng)性能的影響,可以快速拒絕可能導致錯誤的服務調(diào)用扛吞,而不需要等待真正的錯誤返回

熔斷器引入

上面介紹了熔斷器的原理呻惕,在了解完原理后,你是否有思考我們?nèi)绾我肴蹟嗥髂乩谋龋恳环N方案是在業(yè)務邏輯中可以加入熔斷器亚脆,但顯然是不夠優(yōu)雅也不夠通用的,因此我們需要把熔斷器集成在框架內(nèi)盲泛,在zRPC框架內(nèi)就內(nèi)置了熔斷器

我們知道濒持,熔斷器主要是用來保護調(diào)用端,調(diào)用端在發(fā)起請求的時候需要先經(jīng)過熔斷器寺滚,而客戶端攔截器正好兼具了這個這個功能柑营,所以在zRPC框架內(nèi)熔斷器是實現(xiàn)在客戶端攔截器內(nèi),攔截器的原理如下圖:

interceptor.png

對應的代碼為:

func BreakerInterceptor(ctx context.Context, method string, req, reply interface{},
    cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
  // 基于請求方法進行熔斷
    breakerName := path.Join(cc.Target(), method)
    return breaker.DoWithAcceptable(breakerName, func() error {
    // 真正發(fā)起調(diào)用
        return invoker(ctx, method, req, reply, cc, opts...)
    // codes.Acceptable判斷哪種錯誤需要加入熔斷錯誤計數(shù)
    }, codes.Acceptable)
}

熔斷器實現(xiàn)

zRPC中熔斷器的實現(xiàn)參考了Google Sre過載保護算法玛迄,該算法的原理如下:

  • 請求數(shù)量(requests):調(diào)用方發(fā)起請求的數(shù)量總和
  • 請求接受數(shù)量(accepts):被調(diào)用方正常處理的請求數(shù)量

在正常情況下由境,這兩個值是相等的,隨著被調(diào)用方服務出現(xiàn)異常開始拒絕請求蓖议,請求接受數(shù)量(accepts)的值開始逐漸小于請求數(shù)量(requests)虏杰,這個時候調(diào)用方可以繼續(xù)發(fā)送請求,直到requests = K * accepts勒虾,一旦超過這個限制纺阔,熔斷器就回打開,新的請求會在本地以一定的概率被拋棄直接返回錯誤修然,概率的計算公式如下:

client_rejection2.png

通過修改算法中的K(倍值)笛钝,可以調(diào)節(jié)熔斷器的敏感度,當降低該倍值會使自適應熔斷算法更敏感愕宋,當增加該倍值會使得自適應熔斷算法降低敏感度玻靡,舉例來說,假設將調(diào)用方的請求上限從 requests = 2 * acceptst 調(diào)整為 requests = 1.1 * accepts 那么就意味著調(diào)用方每十個請求之中就有一個請求會觸發(fā)熔斷

代碼路徑為go-zero/core/breaker

type googleBreaker struct {
    k     float64  // 倍值 默認1.5
    stat  *collection.RollingWindow // 滑動時間窗口中贝,用來對請求失敗和成功計數(shù)
    proba *mathx.Proba // 動態(tài)概率
}

自適應熔斷算法實現(xiàn)

func (b *googleBreaker) accept() error {
    accepts, total := b.history()  // 請求接受數(shù)量和請求總量
    weightedAccepts := b.k * float64(accepts)
  // 計算丟棄請求概率
    dropRatio := math.Max(0, (float64(total-protection)-weightedAccepts)/float64(total+1))
    if dropRatio <= 0 {
        return nil
    }
    // 動態(tài)判斷是否觸發(fā)熔斷
    if b.proba.TrueOnProba(dropRatio) {
        return ErrServiceUnavailable
    }

    return nil
}

每次發(fā)起請求會調(diào)用doReq方法囤捻,在這個方法中首先通過accept效驗是否觸發(fā)熔斷,acceptable用來判斷哪些error會計入失敗計數(shù)邻寿,定義如下:

func Acceptable(err error) bool {
    switch status.Code(err) {
    case codes.DeadlineExceeded, codes.Internal, codes.Unavailable, codes.DataLoss: // 異常請求錯誤
        return false
    default:
        return true
    }
}

如果請求正常則通過markSuccess把請求數(shù)量和請求接受數(shù)量都加一蝎土,如果請求不正常則只有請求數(shù)量會加一

func (b *googleBreaker) doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error {
    // 判斷是否觸發(fā)熔斷
  if err := b.accept(); err != nil {
        if fallback != nil {
            return fallback(err)
        } else {
            return err
        }
    }

    defer func() {
        if e := recover(); e != nil {
            b.markFailure()
            panic(e)
        }
    }()
    
  // 執(zhí)行真正的調(diào)用
    err := req()
  // 正常請求計數(shù)
    if acceptable(err) {
        b.markSuccess()
    } else {
    // 異常請求計數(shù)
        b.markFailure()
    }

    return err
}

總結

調(diào)用端可以通過熔斷機制進行自我保護,防止調(diào)用下游服務出現(xiàn)異常绣否,或者耗時過長影響調(diào)用端的業(yè)務邏輯誊涯,很多功能完整的微服務框架都會內(nèi)置熔斷器。其實蒜撮,不僅微服務調(diào)用之間需要熔斷器暴构,在調(diào)用依賴資源的時候,比如mysql、redis等也可以引入熔斷器的機制丹壕。

項目地址:
https://github.com/tal-tech/go-zero

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末庆械,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子菌赖,更是在濱河造成了極大的恐慌缭乘,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件琉用,死亡現(xiàn)場離奇詭異堕绩,居然都是意外死亡,警方通過查閱死者的電腦和手機邑时,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進店門奴紧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人晶丘,你說我怎么就攤上這事黍氮。” “怎么了浅浮?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵沫浆,是天一觀的道長。 經(jīng)常有香客問我滚秩,道長专执,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任郁油,我火速辦了婚禮本股,結果婚禮上,老公的妹妹穿的比我還像新娘桐腌。我一直安慰自己拄显,他們只是感情好,可當我...
    茶點故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布案站。 她就那樣靜靜地躺著凿叠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嚼吞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天蹬碧,我揣著相機與錄音舱禽,去河邊找鬼。 笑死恩沽,一個胖子當著我的面吹牛誊稚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼里伯,長吁一口氣:“原來是場噩夢啊……” “哼城瞎!你這毒婦竟也來了?” 一聲冷哼從身側響起疾瓮,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤脖镀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后狼电,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蜒灰,經(jīng)...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年肩碟,在試婚紗的時候發(fā)現(xiàn)自己被綠了强窖。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,146評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡削祈,死狀恐怖翅溺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情髓抑,我是刑警寧澤咙崎,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站启昧,受9級特大地震影響叙凡,放射性物質發(fā)生泄漏。R本人自食惡果不足惜密末,卻給世界環(huán)境...
    茶點故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一握爷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧严里,春花似錦新啼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至迷帜,卻和暖如春物舒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背戏锹。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工冠胯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锦针。 一個月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓荠察,卻偏偏與公主長得像置蜀,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子悉盆,可洞房花燭夜當晚...
    茶點故事閱讀 45,107評論 2 356