国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專(zhuān)欄INFORMATION COLUMN

一文講透自適應(yīng)熔斷的原理和實(shí)現(xiàn)

muddyway / 2367人閱讀

摘要:代碼實(shí)現(xiàn)代碼實(shí)現(xiàn)接下來(lái)思考一個(gè)熔斷器如何實(shí)現(xiàn)。同時(shí)熔斷器的狀態(tài)也需要依靠指標(biāo)統(tǒng)計(jì)來(lái)實(shí)現(xiàn)可觀測(cè)性,我們實(shí)現(xiàn)任何系統(tǒng)第一步需要考慮就是可觀測(cè)性,不然系統(tǒng)就是一個(gè)黑盒。可能是,熔斷器需要實(shí)時(shí)收集此數(shù)據(jù)。熔斷方法,自動(dòng)上報(bào)執(zhí)行結(jié)果自動(dòng)擋。。。

為什么需要熔斷

微服務(wù)集群中,每個(gè)應(yīng)用基本都會(huì)依賴(lài)一定數(shù)量的外部服務(wù)。有可能隨時(shí)都會(huì)遇到網(wǎng)絡(luò)連接緩慢,超時(shí),依賴(lài)服務(wù)過(guò)載,服務(wù)不可用的情況,在高并發(fā)場(chǎng)景下如果此時(shí)調(diào)用方不做任何處理,繼續(xù)持續(xù)請(qǐng)求故障服務(wù)的話很容易引起整個(gè)微服務(wù)集群雪崩。
比如高并發(fā)場(chǎng)景的用戶(hù)訂單服務(wù),一般需要依賴(lài)一下服務(wù):

  1. 商品服務(wù)
  2. 賬戶(hù)服務(wù)
  3. 庫(kù)存服務(wù)

假如此時(shí) 賬戶(hù)服務(wù) 過(guò)載,訂單服務(wù)持續(xù)請(qǐng)求賬戶(hù)服務(wù)只能被動(dòng)的等待賬戶(hù)服務(wù)報(bào)錯(cuò)或者請(qǐng)求超時(shí),進(jìn)而導(dǎo)致訂單請(qǐng)求被大量堆積,這些無(wú)效請(qǐng)求依然會(huì)占用系統(tǒng)資源:cpu,內(nèi)存,數(shù)據(jù)連接...導(dǎo)致訂單服務(wù)整體不可用。即使賬戶(hù)服務(wù)恢復(fù)了訂單服務(wù)也無(wú)法自我恢復(fù)。

這時(shí)如果有一個(gè)主動(dòng)保護(hù)機(jī)制應(yīng)對(duì)這種場(chǎng)景的話訂單服務(wù)至少可以保證自身的運(yùn)行狀態(tài),等待賬戶(hù)服務(wù)恢復(fù)時(shí)訂單服務(wù)也同步自我恢復(fù),這種自我保護(hù)機(jī)制在服務(wù)治理中叫熔斷機(jī)制。

熔斷

熔斷是調(diào)用方自我保護(hù)的機(jī)制(客觀上也能保護(hù)被調(diào)用方),熔斷對(duì)象是外部服務(wù)。

降級(jí)

降級(jí)是被調(diào)用方(服務(wù)提供者)的防止因自身資源不足導(dǎo)致過(guò)載的自我保護(hù)機(jī)制,降級(jí)對(duì)象是自身。

熔斷這一詞來(lái)源時(shí)我們?nèi)粘I铍娐防锩娴娜蹟嗥鳎?dāng)負(fù)載過(guò)高時(shí)(電流過(guò)大)保險(xiǎn)絲會(huì)自行熔斷防止電路被燒壞,很多技術(shù)都是來(lái)自生活場(chǎng)景的提煉。

工作原理

熔斷器一般具有三個(gè)狀態(tài):

  1. 關(guān)閉:默認(rèn)狀態(tài),請(qǐng)求能被到達(dá)目標(biāo)服務(wù),同時(shí)統(tǒng)計(jì)在窗口時(shí)間成功和失敗次數(shù),如果達(dá)到錯(cuò)誤率閾值將會(huì)進(jìn)入斷開(kāi)狀態(tài)。
  2. 斷開(kāi): 此狀態(tài)下將會(huì)直接返回錯(cuò)誤,如果有 fallback 配置則直接調(diào)用 fallback 方法。
  3. 半斷開(kāi):進(jìn)行斷開(kāi)狀態(tài)會(huì)維護(hù)一個(gè)超市時(shí)間,到達(dá)超時(shí)時(shí)間開(kāi)始進(jìn)入 半斷開(kāi) 狀態(tài),嘗試允許一部門(mén)請(qǐng)求正常通過(guò)并統(tǒng)計(jì)成功數(shù)量,如果請(qǐng)求正常則認(rèn)為此時(shí)目標(biāo)服務(wù)已恢復(fù)進(jìn)入 關(guān)閉 狀態(tài),否則進(jìn)入 斷開(kāi) 狀態(tài)。半斷開(kāi) 狀態(tài)存在的目的在于實(shí)現(xiàn)了自我修復(fù),同時(shí)防止正在恢復(fù)的服務(wù)再次被大量打垮。

使用較多的熔斷組件:

  1. hystrix circuit breaker(不再維護(hù))
  2. hystrix-go
  3. resilience4j(推薦)
  4. sentinel(推薦)

什么是自適應(yīng)熔斷

基于上面提到的熔斷器原理,項(xiàng)目中我們要使用好熔斷器通常需要準(zhǔn)備以下參數(shù):

  1. 錯(cuò)誤比例閾值:達(dá)到該閾值進(jìn)入 斷開(kāi) 狀態(tài)。
  2. 斷開(kāi)狀態(tài)超時(shí)時(shí)間:超時(shí)后進(jìn)入 半斷開(kāi) 狀態(tài)。
  3. 半斷開(kāi)狀態(tài)允許請(qǐng)求數(shù)量。
  4. 窗口時(shí)間大小。

實(shí)際上可選的配置參數(shù)還有非常非常多,參考 https://resilience4j.readme.io/docs/circuitbreaker

對(duì)于經(jīng)驗(yàn)不夠豐富的開(kāi)發(fā)人員而言,這些參數(shù)設(shè)置多少合適心里其實(shí)并沒(méi)有底。

那么有沒(méi)有一種自適應(yīng)的熔斷算法能讓我們不關(guān)注參數(shù),只要簡(jiǎn)單配置就能滿(mǎn)足大部分場(chǎng)景?

其實(shí)是有的,google sre提供了一種自適應(yīng)熔斷算法來(lái)計(jì)算丟棄請(qǐng)求的概率:

算法參數(shù):

  1. requests: 窗口時(shí)間內(nèi)的請(qǐng)求總數(shù)
  2. accepts:正常請(qǐng)求數(shù)量
  3. K:敏感度,K 越小越容易丟請(qǐng)求,一般推薦 1.5-2 之間

算法解釋?zhuān)?/p>

  1. 正常情況下 requests=accepts,所以概率是 0。
  2. 隨著正常請(qǐng)求數(shù)量減少,當(dāng)達(dá)到 requests == K* accepts 繼續(xù)請(qǐng)求時(shí),概率 P 會(huì)逐漸比 0 大開(kāi)始按照概率逐漸丟棄一些請(qǐng)求,如果故障嚴(yán)重則丟包會(huì)越來(lái)越多,假如窗口時(shí)間內(nèi) accepts==0 則完全熔斷。
  3. 當(dāng)應(yīng)用逐漸恢復(fù)正常時(shí),accepts、requests 同時(shí)都在增加,但是 K*accepts 會(huì)比 requests 增加的更快,所以概率很快就會(huì)歸 0,關(guān)閉熔斷。

代碼實(shí)現(xiàn)

接下來(lái)思考一個(gè)熔斷器如何實(shí)現(xiàn)。

初步思路是:

  1. 無(wú)論什么熔斷器都得依靠指標(biāo)統(tǒng)計(jì)來(lái)轉(zhuǎn)換狀態(tài),而統(tǒng)計(jì)指標(biāo)一般要求是最近的一段時(shí)間內(nèi)的數(shù)據(jù)(太久的數(shù)據(jù)沒(méi)有參考意義也浪費(fèi)空間),所以通常采用一個(gè) 滑動(dòng)時(shí)間窗口 數(shù)據(jù)結(jié)構(gòu) 來(lái)存儲(chǔ)統(tǒng)計(jì)數(shù)據(jù)。同時(shí)熔斷器的狀態(tài)也需要依靠指標(biāo)統(tǒng)計(jì)來(lái)實(shí)現(xiàn)可觀測(cè)性,我們實(shí)現(xiàn)任何系統(tǒng)第一步需要考慮就是可觀測(cè)性,不然系統(tǒng)就是一個(gè)黑盒。
  2. 外部服務(wù)請(qǐng)求結(jié)果各式各樣,所以需要提供一個(gè)自定義的判斷方法,判斷請(qǐng)求是否成功。可能是 http.code 、rpc.code、body.code,熔斷器需要實(shí)時(shí)收集此數(shù)據(jù)。
  3. 當(dāng)外部服務(wù)被熔斷時(shí)使用者往往需要自定義快速失敗的邏輯,考慮提供自定義的 fallback() 功能。

下面來(lái)逐步分析 go-zero 的源碼實(shí)現(xiàn):

core/breaker/breaker.go

熔斷器接口定義

兵馬未動(dòng),糧草先行,明確了需求后就可以開(kāi)始規(guī)劃定義接口了,接口是我們編碼思維抽象的第一步也是最重要的一步。

核心定義包含兩種類(lèi)型的方法:

Allow():需要手動(dòng)回調(diào)請(qǐng)求結(jié)果至熔斷器,相當(dāng)于手動(dòng)擋。

DoXXX():自動(dòng)回調(diào)請(qǐng)求結(jié)果至熔斷器,相當(dāng)于自動(dòng)擋,實(shí)際上 DoXXX() 類(lèi)型方法最后都是調(diào)用
DoWithFallbackAcceptable(req func() error, fallback func(err error) error, acceptable Acceptable) error

	// 自定義判定執(zhí)行結(jié)果	Acceptable func(err error) bool		// 手動(dòng)回調(diào)	Promise interface {		// Accept tells the Breaker that the call is successful.		// 請(qǐng)求成功		Accept()		// Reject tells the Breaker that the call is failed.		// 請(qǐng)求失敗		Reject(reason string)	}		Breaker interface {		// 熔斷器名稱(chēng)		Name() string		// 熔斷方法,執(zhí)行請(qǐng)求時(shí)必須手動(dòng)上報(bào)執(zhí)行結(jié)果		// 適用于簡(jiǎn)單無(wú)需自定義快速失敗,無(wú)需自定義判定請(qǐng)求結(jié)果的場(chǎng)景		// 相當(dāng)于手動(dòng)擋。。。		Allow() (Promise, error)		// 熔斷方法,自動(dòng)上報(bào)執(zhí)行結(jié)果		// 自動(dòng)擋。。。		Do(req func() error) error		// 熔斷方法		// acceptable - 支持自定義判定執(zhí)行結(jié)果		DoWithAcceptable(req func() error, acceptable Acceptable) error		// 熔斷方法		// fallback - 支持自定義快速失敗		DoWithFallback(req func() error, fallback func(err error) error) error		// 熔斷方法		// fallback - 支持自定義快速失敗		// acceptable - 支持自定義判定執(zhí)行結(jié)果		DoWithFallbackAcceptable(req func() error, fallback func(err error) error, acceptable Acceptable) error	}

熔斷器實(shí)現(xiàn)

circuitBreaker 繼承 throttle,實(shí)際上這里相當(dāng)于靜態(tài)代理,代理模式可以在不改變?cè)袑?duì)象的基礎(chǔ)上增強(qiáng)功能,后面我們會(huì)看到 go-zero 這樣做的原因是為了收集熔斷器錯(cuò)誤數(shù)據(jù),也就是為了實(shí)現(xiàn)可觀測(cè)性。

熔斷器實(shí)現(xiàn)采用靜態(tài)代理模式,看起來(lái)稍微有點(diǎn)繞腦。

// 熔斷器結(jié)構(gòu)體circuitBreaker struct {	name string	// 實(shí)際上 circuitBreaker熔斷功能都代理給 throttle來(lái)實(shí)現(xiàn)	throttle}// 熔斷器接口throttle interface {	// 熔斷方法	allow() (Promise, error)	// 熔斷方法	// DoXXX()方法最終都會(huì)該方法	doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error}	func (cb *circuitBreaker) Allow() (Promise, error) { 	return cb.throttle.allow()}    func (cb *circuitBreaker) Do(req func() error) error {  return cb.throttle.doReq(req, nil, defaultAcceptable)}    func (cb *circuitBreaker) DoWithAcceptable(req func() error, acceptable Acceptable) error {  return cb.throttle.doReq(req, nil, acceptable)}    func (cb *circuitBreaker) DoWithFallback(req func() error, fallback func(err error) error) error {  return cb.throttle.doReq(req, fallback, defaultAcceptable)}    func (cb *circuitBreaker) DoWithFallbackAcceptable(req func() error, fallback func(err error) error,  acceptable Acceptable) error {    return cb.throttle.doReq(req, fallback, acceptable)}		

throttle 接口實(shí)現(xiàn)類(lèi):

loggedThrottle 增加了為了收集錯(cuò)誤日志的滾動(dòng)窗口,目的是為了收集當(dāng)請(qǐng)求失敗時(shí)的錯(cuò)誤日志。

// 帶日志功能的熔斷器type loggedThrottle struct {	// 名稱(chēng)	name string	// 代理對(duì)象	internalThrottle	// 滾動(dòng)窗口,滾動(dòng)收集數(shù)據(jù),相當(dāng)于環(huán)形數(shù)組	errWin *errorWindow}// 熔斷方法func (lt loggedThrottle) allow() (Promise, error) {	promise, err := lt.internalThrottle.allow()	return promiseWithReason{		promise: promise,		errWin:  lt.errWin,	}, lt.logError(err)}// 熔斷方法func (lt loggedThrottle) doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error {	return lt.logError(lt.internalThrottle.doReq(req, fallback, func(err error) bool {		accept := acceptable(err)		if !accept {			lt.errWin.add(err.Error())		}		return accept	}))}func (lt loggedThrottle) logError(err error) error {	if err == ErrServiceUnavailable {		// if circuit open, not possible to have empty error window		stat.Report(fmt.Sprintf(			"proc(%s/%d), callee: %s, breaker is open and requests dropped/nlast errors:/n%s",			proc.ProcessName(), proc.Pid(), lt.name, lt.errWin))	}	return err}

錯(cuò)誤日志收集 errorWindow

errorWindow 是一個(gè)環(huán)形數(shù)組,新數(shù)據(jù)不斷滾動(dòng)覆蓋最舊的數(shù)據(jù),通過(guò)取余實(shí)現(xiàn)。

// 滾動(dòng)窗口type errorWindow struct {	reasons [numHistoryReasons]string	index   int	count   int	lock    sync.Mutex}// 添加數(shù)據(jù)func (ew *errorWindow) add(reason string) {	ew.lock.Lock()	// 添加錯(cuò)誤日志	ew.reasons[ew.index] = fmt.Sprintf("%s %s", timex.Time().Format(timeFormat), reason)	// 更新index,為下一次寫(xiě)入數(shù)據(jù)做準(zhǔn)備	// 這里用的取模實(shí)現(xiàn)了滾動(dòng)功能	ew.index = (ew.index + 1) % numHistoryReasons	// 統(tǒng)計(jì)數(shù)量	ew.count = mathx.MinInt(ew.count+1, numHistoryReasons)	ew.lock.Unlock()}// 格式化錯(cuò)誤日志func (ew *errorWindow) String() string {	var reasons []string	ew.lock.Lock()	// reverse order	for i := ew.index - 1; i >= ew.index-ew.count; i-- {		reasons = append(reasons, ew.reasons[(i+numHistoryReasons)%numHistoryReasons])	}	ew.lock.Unlock()	return strings.Join(reasons, "/n")}

看到這里我們還沒(méi)看到實(shí)際的熔斷器實(shí)現(xiàn),實(shí)際上真正的熔斷操作被代理給了 internalThrottle 對(duì)象。

	internalThrottle interface {		allow() (internalPromise, error)		doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error	}

internalThrottle 接口實(shí)現(xiàn) googleBreaker 結(jié)構(gòu)體定義

type googleBreaker struct {	// 敏感度,go-zero中默認(rèn)值為1.5	k float64	// 滑動(dòng)窗口,用于記錄最近一段時(shí)間內(nèi)的請(qǐng)求總數(shù),成功總數(shù)	stat *collection.RollingWindow	// 概率生成器	// 隨機(jī)產(chǎn)生0.0-1.0之間的雙精度浮點(diǎn)數(shù)	proba *mathx.Proba}

可以看到熔斷器屬性其實(shí)非常簡(jiǎn)單,數(shù)據(jù)統(tǒng)計(jì)采用的是滑動(dòng)時(shí)間窗口來(lái)實(shí)現(xiàn)。

RollingWindow 滑動(dòng)窗口

滑動(dòng)窗口屬于比較通用的數(shù)據(jù)結(jié)構(gòu),常用于最近一段時(shí)間內(nèi)的行為數(shù)據(jù)統(tǒng)計(jì)。

它的實(shí)現(xiàn)非常有意思,尤其是如何模擬窗口滑動(dòng)過(guò)程。

先來(lái)看滑動(dòng)窗口的結(jié)構(gòu)體定義:

	RollingWindow struct {		// 互斥鎖		lock sync.RWMutex		// 滑動(dòng)窗口數(shù)量		size int		// 窗口,數(shù)據(jù)容器		win *window		// 滑動(dòng)窗口單元時(shí)間間隔		interval time.Duration		// 游標(biāo),用于定位當(dāng)前應(yīng)該寫(xiě)入哪個(gè)bucket		offset int		// 匯總數(shù)據(jù)時(shí),是否忽略當(dāng)前正在寫(xiě)入桶的數(shù)據(jù)		// 某些場(chǎng)景下因?yàn)楫?dāng)前正在寫(xiě)入的桶數(shù)據(jù)并沒(méi)有經(jīng)過(guò)完整的窗口時(shí)間間隔		// 可能導(dǎo)致當(dāng)前桶的統(tǒng)計(jì)并不準(zhǔn)確		ignoreCurrent bool		// 最后寫(xiě)入桶的時(shí)間		// 用于計(jì)算下一次寫(xiě)入數(shù)據(jù)間隔最后一次寫(xiě)入數(shù)據(jù)的之間		// 經(jīng)過(guò)了多少個(gè)時(shí)間間隔		lastTime      time.Duration 	}

window 是數(shù)據(jù)的實(shí)際存儲(chǔ)位置,其實(shí)就是一個(gè)數(shù)組,提供向指定 offset 添加數(shù)據(jù)與清除操作。
數(shù)組里面按照 internal 時(shí)間間隔分隔成多個(gè) bucket。

// 時(shí)間窗口type window struct {	// 桶	// 一個(gè)桶標(biāo)識(shí)一個(gè)時(shí)間間隔	buckets []*Bucket	// 窗口大小	size int}// 添加數(shù)據(jù)// offset - 游標(biāo),定位寫(xiě)入bucket位置// v - 行為數(shù)據(jù)func (w *window) add(offset int, v float64) {	w.buckets[offset%w.size].add(v)}// 匯總數(shù)據(jù)// fn - 自定義的bucket統(tǒng)計(jì)函數(shù)func (w *window) reduce(start, count int, fn func(b *Bucket)) {	for i := 0; i < count; i++ {		fn(w.buckets[(start+i)%w.size])	}}// 清理特定bucketfunc (w *window) resetBucket(offset int) {	w.buckets[offset%w.size].reset()}// 桶type Bucket struct {	// 當(dāng)前桶內(nèi)值之和	Sum float64	// 當(dāng)前桶的add總次數(shù)	Count int64}// 向桶添加數(shù)據(jù)func (b *Bucket) add(v float64) {	// 求和	b.Sum += v	// 次數(shù)+1	b.Count++}// 桶數(shù)據(jù)清零func (b *Bucket) reset() {	b.Sum = 0	b.Count = 0}

window 添加數(shù)據(jù):

  1. 計(jì)算當(dāng)前時(shí)間距離上次添加時(shí)間經(jīng)過(guò)了多少個(gè) 時(shí)間間隔,實(shí)際上就是過(guò)期了幾個(gè) bucket。
  2. 清理過(guò)期桶的數(shù)據(jù)
  3. 更新 offset,更新 offset 的過(guò)程實(shí)際上就是在模擬窗口滑動(dòng)
  4. 添加數(shù)據(jù)

// 添加數(shù)據(jù)func (rw *RollingWindow) Add(v float64) {	rw.lock.Lock()	defer rw.lock.Unlock()	// 獲取當(dāng)前寫(xiě)入的下標(biāo)	rw.updateOffset()	// 添加數(shù)據(jù)	rw.win.add(rw.offset, v)}// 計(jì)算當(dāng)前距離最后寫(xiě)入數(shù)據(jù)經(jīng)過(guò)多少個(gè)單元時(shí)間間隔// 實(shí)際上指的就是經(jīng)過(guò)多少個(gè)桶func (rw *RollingWindow) span() int {	offset := int(timex.Since(rw.lastTime) / rw.interval)	if 0 <= offset && offset < rw.size {		return offset	}	// 大于時(shí)間窗口時(shí) 返回窗口大小即可	return rw.size}// 更新當(dāng)前時(shí)間的offset// 實(shí)現(xiàn)窗口滑動(dòng)func (rw *RollingWindow) updateOffset() {	// 經(jīng)過(guò)span個(gè)桶的時(shí)間	span := rw.span()	// 還在同一單元時(shí)間內(nèi)不需要更新	if span <= 0 {		return	}	offset := rw.offset	// 既然經(jīng)過(guò)了span個(gè)桶的時(shí)間沒(méi)有寫(xiě)入數(shù)據(jù)	// 那么這些桶內(nèi)的數(shù)據(jù)就不應(yīng)該繼續(xù)保留了,屬于過(guò)期數(shù)據(jù)清空即可	// 可以看到這里全部用的 % 取余操作,可以實(shí)現(xiàn)按照下標(biāo)周期性寫(xiě)入	// 如果超出下標(biāo)了那就從頭開(kāi)始寫(xiě),確保新數(shù)據(jù)一定能夠正常寫(xiě)入	// 類(lèi)似循環(huán)數(shù)組的效果	for i := 0; i < span; i++ {		rw.win.resetBucket((offset + i + 1) % rw.size)	}	// 更新offset	rw.offset = (offset + span) % rw.size	now := timex.Now()	// 更新操作時(shí)間	// 這里很有意思	rw.lastTime = now - (now-rw.lastTime)%rw.interval}

window 統(tǒng)計(jì)數(shù)據(jù):

// 歸納匯總數(shù)據(jù)func (rw *RollingWindow) Reduce(fn func(b *Bucket)) {	rw.lock.RLock()	defer rw.lock.RUnlock()	var diff int	span := rw.span()	// 當(dāng)前時(shí)間截止前,未過(guò)期桶的數(shù)量	if span == 0 && rw.ignoreCurrent {		diff = rw.size - 1	} else {		diff = rw.size - span	}	if diff > 0 {		// rw.offset - rw.offset+span之間的桶數(shù)據(jù)是過(guò)期的不應(yīng)該計(jì)入統(tǒng)計(jì)		offset := (rw.offset + span + 1) % rw.size		// 匯總數(shù)據(jù)		rw.win.reduce(offset, diff, fn)	}}

googleBreaker 判斷是否應(yīng)該熔斷

  1. 收集滑動(dòng)窗口內(nèi)的統(tǒng)計(jì)數(shù)據(jù)
  2. 計(jì)算熔斷概率
// 按照最近一段時(shí)間的請(qǐng)求數(shù)據(jù)計(jì)算是否熔斷func (b *googleBreaker) accept() error {	// 獲取最近一段時(shí)間的統(tǒng)計(jì)數(shù)據(jù)	accepts, total := b.history()	// 計(jì)算動(dòng)態(tài)熔斷概率	weightedAccepts := b.k * float64(accepts)	// https://landing.google.com/sre/sre-book/chapters/handling-overload/#eq2101	dropRatio := math.Max(0, (float64(total-protection)-weightedAccepts)/float64(total+1))	// 概率為0,通過(guò)	if dropRatio <= 0 {		return nil	}	// 隨機(jī)產(chǎn)生0.0-1.0之間的隨機(jī)數(shù)與上面計(jì)算出來(lái)的熔斷概率相比較	// 如果隨機(jī)數(shù)比熔斷概率小則進(jìn)行熔斷	if b.proba.TrueOnProba(dropRatio) {		return ErrServiceUnavailable	}	return nil}

googleBreaker 熔斷邏輯實(shí)現(xiàn)

熔斷器對(duì)外暴露兩種類(lèi)型的方法

  1. 簡(jiǎn)單場(chǎng)景直接判斷對(duì)象是否被熔斷,執(zhí)行請(qǐng)求后必須需手動(dòng)上報(bào)執(zhí)行結(jié)果至熔斷器。

func (b *googleBreaker) allow() (internalPromise, error)

  1. 復(fù)雜場(chǎng)景下支持自定義快速失敗,自定義判定請(qǐng)求是否成功的熔斷方法,自動(dòng)上報(bào)執(zhí)行結(jié)果至熔斷器。

func (b *googleBreaker) doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error

Acceptable 參數(shù)目的是自定義判斷請(qǐng)求是否成功。

Acceptable func(err error) bool
// 熔斷方法// 返回一個(gè)promise異步回調(diào)對(duì)象,可由開(kāi)發(fā)者自行決定是否上報(bào)結(jié)果到熔斷器func (b *googleBreaker) allow() (internalPromise, error) {	if err := b.accept(); err != nil {		return nil, err	}	return googlePromise{		b: b,	}, nil}// 熔斷方法// req - 熔斷對(duì)象方法// fallback - 自定義快速失敗函數(shù),可對(duì)熔斷產(chǎn)生的err進(jìn)行包裝后返回// acceptable - 對(duì)本次未熔斷時(shí)執(zhí)行請(qǐng)求的結(jié)果進(jìn)行自定義的判定,比如可以針對(duì)http.code,rpc.code,body.codefunc (b *googleBreaker) doReq(req func() error, fallback func(err error) error, acceptable Acceptable) error {	// 判定是否熔斷	if err := b.accept(); err != nil {		// 熔斷中,如果有自定義的fallback則執(zhí)行		if fallback != nil {			return fallback(err)		}		return err	}	// 如果執(zhí)行req()過(guò)程發(fā)生了panic,依然判定本次執(zhí)行失敗上報(bào)至熔斷器	defer func() {		if e := recover(); e != nil {			b.markFailure()			panic(e)		}	}()	// 執(zhí)行請(qǐng)求	err := req()	// 判定請(qǐng)求成功	if acceptable(err) {		b.markSuccess()	} else {		b.markFailure()	}	return err}// 上報(bào)成功func (b *googleBreaker) markSuccess() {	b.stat.Add(1)}// 上報(bào)失敗func (b *googleBreaker) markFailure() {	b.stat.Add(0)}// 統(tǒng)計(jì)數(shù)據(jù)func (b *googleBreaker) history() (accepts, total int64) {	b.stat.Reduce(func(b *collection.Bucket) {		accepts += int64(b.Sum)		total += b.Count	})	return}

資料

微軟 azure 關(guān)于熔斷器設(shè)計(jì)模式

索尼參考微軟的文檔開(kāi)源的熔斷器實(shí)現(xiàn)

go-zero 自適應(yīng)熔斷器文檔

項(xiàng)目地址

https://github.com/zeromicro/go-zero

歡迎使用 go-zerostar 支持我們!

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/123760.html

相關(guān)文章

  • 一文講透前端開(kāi)發(fā)所需網(wǎng)絡(luò)知識(shí)

    摘要:這時(shí)候超過(guò)定時(shí)器設(shè)定的時(shí)間就會(huì)再次發(fā)送丟包的數(shù)據(jù)直到對(duì)端響應(yīng),所以需要每次都備份發(fā)送的數(shù)據(jù)。 UDP 面向報(bào)文 UDP 是一個(gè)面向報(bào)文(報(bào)文可以理解為一段段的數(shù)據(jù))的協(xié)議。意思就是 UDP 只是報(bào)文的搬運(yùn)工,不會(huì)對(duì)報(bào)文進(jìn)行任何拆分和拼接操作。 具體來(lái)說(shuō) 在發(fā)送端,應(yīng)用層將數(shù)據(jù)傳遞給傳輸層的 UDP 協(xié)議,UDP 只會(huì)給數(shù)據(jù)增加一個(gè) UDP 頭標(biāo)識(shí)下是 UDP 協(xié)議,然后就傳遞給網(wǎng)絡(luò)層...

    smallStone 評(píng)論0 收藏0
  • 一文講透前端開(kāi)發(fā)所需網(wǎng)絡(luò)知識(shí)

    摘要:這時(shí)候超過(guò)定時(shí)器設(shè)定的時(shí)間就會(huì)再次發(fā)送丟包的數(shù)據(jù)直到對(duì)端響應(yīng),所以需要每次都備份發(fā)送的數(shù)據(jù)。 UDP 面向報(bào)文 UDP 是一個(gè)面向報(bào)文(報(bào)文可以理解為一段段的數(shù)據(jù))的協(xié)議。意思就是 UDP 只是報(bào)文的搬運(yùn)工,不會(huì)對(duì)報(bào)文進(jìn)行任何拆分和拼接操作。 具體來(lái)說(shuō) 在發(fā)送端,應(yīng)用層將數(shù)據(jù)傳遞給傳輸層的 UDP 協(xié)議,UDP 只會(huì)給數(shù)據(jù)增加一個(gè) UDP 頭標(biāo)識(shí)下是 UDP 協(xié)議,然后就傳遞給網(wǎng)絡(luò)層...

    Backache 評(píng)論0 收藏0
  • 且聽(tīng)我一個(gè)故事講透一個(gè)鎖原理之synchronized

    摘要:第三天,太監(jiān)傳話欽天監(jiān)求見(jiàn)一日無(wú)事。第四天,欽天監(jiān)一日無(wú)事。然后所有的競(jìng)爭(zhēng)線程放棄自旋,逐個(gè)插入到對(duì)象里的一個(gè)隊(duì)列尾部,進(jìn)入阻塞狀態(tài)。 微信公眾號(hào):IT一刻鐘大型現(xiàn)實(shí)非嚴(yán)肅主義現(xiàn)場(chǎng)一刻鐘與你分享優(yōu)質(zhì)技術(shù)架構(gòu)與見(jiàn)聞,做一個(gè)有劇情的程序員關(guān)注可第一時(shí)間了解更多精彩內(nèi)容,定期有福利相送喲。 showImg(https://segmentfault.com/img/bVbrgsJ?w=900...

    gougoujiang 評(píng)論0 收藏0
  • 這個(gè)注解一次搞定限流與熔斷降級(jí):@SentinelResource

    摘要:實(shí)現(xiàn)熔斷降級(jí)注解除了可以用來(lái)做限流控制之外,還能實(shí)現(xiàn)與類(lèi)似的熔斷降級(jí)策略。函數(shù)簽名要求返回值類(lèi)型必須與原函數(shù)返回值類(lèi)型一致方法參數(shù)列表需要為空,或者可以額外多一個(gè)類(lèi)型的參數(shù)用于接收對(duì)應(yīng)的異常。若未配置和,則被限流降級(jí)時(shí)會(huì)將直接拋出。 在之前的《使用Sentinel實(shí)現(xiàn)接口限流》一文中,我們僅依靠引入Spring Cloud Alibaba對(duì)Sentinel的整合封裝spring-clo...

    Lionad-Morotar 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<