本篇文章給大家分享的是有關(guān)golang中怎么實(shí)現(xiàn)一個(gè)熔斷器,小編覺得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
閻良網(wǎng)站建設(shè)公司創(chuàng)新互聯(lián),閻良網(wǎng)站設(shè)計(jì)制作,有大型網(wǎng)站制作公司豐富經(jīng)驗(yàn)。已為閻良超過千家提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\成都外貿(mào)網(wǎng)站建設(shè)公司要多少錢,請(qǐng)找那個(gè)售后服務(wù)好的閻良做網(wǎng)站的公司定做!
熔斷器名稱 | hystrix | go-breaker |
---|---|---|
滑動(dòng)窗口計(jì)數(shù) | 支持 | 不支持 |
限流 | 支持 | 不支持 |
阻塞讀 | 是 | 是 |
對(duì)半開啟的處理 | 滑動(dòng)計(jì)數(shù)器+閾值 | 連續(xù)成功則轉(zhuǎn)移到close |
對(duì)監(jiān)控的支持 | 支持metric采集 | 不支持 |
降級(jí)處理 | 支持hook | 支持hook |
解決并發(fā)尖刺 | 不支持 | 支持 |
代碼結(jié)構(gòu)易讀性 | 稍差 | 較好 |
我們一一展開來講
計(jì)數(shù)模塊是熔斷器的核心,網(wǎng)上有針對(duì)計(jì)數(shù)器的大篇幅的分析針對(duì)這里引用知乎上一位大佬的比較類型的文章,根據(jù)最后的比較我們選擇滑動(dòng)窗口的算法來完成計(jì)數(shù)需求。 在hystrix的設(shè)計(jì)中,滑動(dòng)窗口的比較重要的是寫入時(shí)刻和讀取時(shí)刻,因?yàn)槲覀兒苋菀紫氲皆谶@兩個(gè)環(huán)節(jié)涉及到對(duì)一塊內(nèi)存并發(fā)讀寫的問題,首先我們不建議采用go-breaker的全加鎖(讀寫都加鎖)的設(shè)計(jì),因?yàn)殒i在發(fā)生競(jìng)爭(zhēng)時(shí)會(huì)掛起線程,從而降低了CPU的使用率和共享內(nèi)存總線上的同步通信量,那么我們參考hystrix,采用異步提交的方法,也就是將結(jié)果放入一個(gè)隊(duì)列中,不斷消費(fèi)這個(gè)隊(duì)列,這么做有幾點(diǎn)好處
消息串行化,減少寫入讀取數(shù)據(jù)不必要競(jìng)爭(zhēng) 在數(shù)據(jù)生產(chǎn)層--->數(shù)據(jù)存儲(chǔ)層中間構(gòu)造出中間層,方便進(jìn)行監(jiān)控統(tǒng)計(jì)收集等操作 方便控制消息的消費(fèi)情況
在實(shí)現(xiàn)上采用channel的數(shù)據(jù)結(jié)構(gòu),消費(fèi)有高效保證。但是事物都有兩面性,這種設(shè)計(jì)帶來的問題有
滑動(dòng)窗口統(tǒng)計(jì)需要訪問當(dāng)前窗口內(nèi)所有數(shù)據(jù) 串行化沒有將統(tǒng)計(jì)性能發(fā)揮最大(雖然在計(jì)數(shù)豐方面表現(xiàn)很快) 業(yè)務(wù)要允許流量尖刺的出現(xiàn)(假設(shè)沒有加限流)
其中2,3點(diǎn)經(jīng)過調(diào)研都在業(yè)務(wù)允許范圍內(nèi),且針對(duì)第三點(diǎn)我們可以增加限流策略來完善這一點(diǎn)。
hystrix天生限流,所有請(qǐng)求先過令牌桶然后進(jìn)入熔斷統(tǒng)計(jì),go-breaker還沒有這方面支持,在限流這里我們懷疑要不要在一起做(畢竟熔斷是熔斷,限流是限流),所以做了另一個(gè)方案,在半開啟時(shí)進(jìn)行限流放行請(qǐng)求,這樣比較符合半開啟時(shí)的請(qǐng)求通過策略,同時(shí)進(jìn)行統(tǒng)計(jì),限流策略采用退化版本令牌桶,方法如下:
type limitPoolManager struct { max int tickets chan *struct{} lock *sync.RWMutex } /* 方法返回一個(gè)限流器 */ func NewLimitPoolManager(max int) *limitPoolManager { lpm := new(limitPoolManager) tickets := make(chan *struct{}, max) for i := 0; i < max; i++ { tickets <- &struct{}{} } lpm.max = max lpm.tickets = tickets lpm.lock = &sync.RWMutex{} return lpm } /* 方法填充限流器所有令牌 */ func (this *limitPoolManager) ReturnAll() { this.lock.Lock() defer this.lock.Unlock() if len(this.tickets) == 0 { for i := 0; i < this.max; i++ { this.tickets <- &struct{}{} } } } /* 方法返回一個(gè)令牌,得到令牌返回true,令牌用完后返回false */ func (this *limitPoolManager) GetTicket() bool { this.lock.RLock() defer this.lock.RUnlock() select { case <-this.tickets: return true default: return false } } /* 方法返回剩余令牌數(shù) */ func (this *limitPoolManager) GetRemaind() int { this.lock.RLock() defer this.lock.RUnlock() return len(this.tickets) }
因?yàn)榇谢O(shè)計(jì)所以在每次收失敗請(qǐng)求時(shí)可以對(duì)窗口內(nèi)數(shù)據(jù)進(jìn)行錯(cuò)誤率轉(zhuǎn)化。避免hystrix與go-breaker的鎖爭(zhēng)搶
以上+本節(jié)主流程基本完結(jié),現(xiàn)梳理整個(gè)流程明確half-open時(shí)處理:
當(dāng)熔斷器為close時(shí)。只有當(dāng)出現(xiàn)錯(cuò)誤請(qǐng)求時(shí),才進(jìn)行錯(cuò)誤率統(tǒng)計(jì),統(tǒng)計(jì)過閾值則狀態(tài)轉(zhuǎn)移到open,正確請(qǐng)求則正常計(jì)數(shù)。 當(dāng)熔斷器為half-open時(shí),僅當(dāng)令牌桶中還有令牌時(shí)接收請(qǐng)求否則熔斷。令牌桶中還有令牌時(shí),出現(xiàn)錯(cuò)誤請(qǐng)求則更新熔斷休眠時(shí)間并返回所有令牌等待下次半開啟,正常請(qǐng)求則進(jìn)入半開啟時(shí)統(tǒng)計(jì)達(dá)到閾值則狀態(tài)轉(zhuǎn)移到close。 當(dāng)熔斷器為open時(shí),僅當(dāng)熔斷休眠時(shí)間小于當(dāng)前時(shí)間時(shí),當(dāng)熔斷器狀態(tài)轉(zhuǎn)移到half-open,可以進(jìn)行第二條,否則執(zhí)行熔斷
首先判斷是否是半開啟狀態(tài)
switch this.counter.GetStatus() { case STATE_OPEN: if this.cycleTime < time.Now().Local().Unix() { return OPEN_TO_HALF_ERROR } return BREAKER_OPEN_ERROR }
其次如果是半開啟狀態(tài)則取令牌,取到令牌則執(zhí)行請(qǐng)求,進(jìn)入熔斷時(shí)計(jì)數(shù),否則直接熔斷
/*取令牌*/ if !this.lpm.GetTicket() { this.safelCalllback(fallback, BREAKER_OPEN_ERROR) return nil } /*執(zhí)行方法*/ runErr := run() if runErr != nil { this.fail() this.safelCalllback(fallback, runErr) return runErr } this.success() return nil
流量尖刺的削峰伴隨著限流的邏輯,所以可以在請(qǐng)求到達(dá)時(shí)優(yōu)先進(jìn)入令牌桶
提供hook函數(shù),在限流或者執(zhí)行失敗時(shí)可以提供降級(jí)或者回掉
/* 執(zhí)行函數(shù) */ type runFunc func() error /* 回調(diào)函數(shù) */ type fallbackFunc func(error) /* Do方法結(jié)合熔斷策略執(zhí)行run函數(shù) 其中參數(shù)包括:上下文ctx,策略名name,將要執(zhí)行方法run,以及回調(diào)函數(shù)fallback.其中ctx,name,run必傳 run函數(shù)的錯(cuò)誤會(huì)直接同步返回,回調(diào)函數(shù)fallback接收除了run錯(cuò)誤以外還會(huì)接收熔斷時(shí)錯(cuò)誤,調(diào)用方如果需要降級(jí)可在fallback中自己判斷 */ func Do(ctx context.Context, name string, run runFunc, fallback fallbackFunc) error { ........ }
以上就是golang中怎么實(shí)現(xiàn)一個(gè)熔斷器,小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見到或用到的。希望你能通過這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。