线上防雪崩利器——熔断器设计原理与实现
轉(zhuǎn)載自??線上防雪崩利器——熔斷器設(shè)計原理與實(shí)現(xiàn)
本文來自作者投稿,作者林灣村龍貓,這是一篇他根據(jù)工作中遇到的問題總結(jié)出的最佳實(shí)踐。
上周六,我負(fù)責(zé)的業(yè)務(wù)在凌晨00-04點(diǎn)的支付全部失敗了。
結(jié)果一查,MD,晚上銀行維護(hù),下游支付系統(tǒng)沒有掛維護(hù)公告,在此期間一直請求維護(hù)中的銀行,當(dāng)然所有返回就是失敗了,有種欲哭無淚的感覺,鍋?zhàn)寴I(yè)務(wù)來背。
為了杜絕在此出現(xiàn)這種大面積批量的支付失敗情況發(fā)生,保障系統(tǒng)的健壯性。我需要個在集中性異常的時候可以終止請求,當(dāng)服務(wù)恢復(fù),恢復(fù)請求。
我想了一些方式,最后,覺得熔斷器比較適合干這種事情。
狀態(tài)模式
在狀態(tài)模式中,我們創(chuàng)建表示各種狀態(tài)的對象和一個行為隨著狀態(tài)對象改變而改變的 context 對象。
我們已一個開關(guān)為例
/***?User:?Rudy?Tan*?Date:?2018/9/22*/public?class?Main{public?static?void?main(String[]?args){Context?context?=?new?Context();context.state?=?new?CloseState();context.switchState();context.switchState();context.switchState();context.switchState();context.switchState();} }/***?狀態(tài)的抽象*/ interface?State{void?switchState(Context?context); }/***?狀態(tài)上下文*/ class?Context{public?State?state;void?switchState(){state.switchState(this);} }/***?開狀態(tài)**/ class?OpenState?implements?State{public?void?switchState(Context?context)?{System.out.println("當(dāng)前狀態(tài):開");context.state?=?new?CloseState();} }/***?關(guān)狀態(tài)**/ class?CloseState?implements?State{public?void?switchState(Context?context)?{System.out.println("當(dāng)前狀態(tài):關(guān)");context.state?=?new?OpenState();} }在每一種狀態(tài)下,context不必關(guān)心每一種狀態(tài)下的行為。交給每一種狀態(tài)自己處理。
?
熔斷器基本原理
熔斷器是當(dāng)依賴的服務(wù)已經(jīng)出現(xiàn)故障時,為了保證自身服務(wù)的正常運(yùn)行不再訪問依賴的服務(wù),防止雪崩效應(yīng)
熔斷器本身就是一個狀態(tài)機(jī)。
關(guān)閉狀態(tài):熔斷器的初始化狀態(tài),該狀態(tài)下允許請求通過。當(dāng)失敗超過閥值,轉(zhuǎn)入打開狀態(tài),
打開狀態(tài):熔斷狀態(tài),該狀態(tài)下不允許請求通過,當(dāng)進(jìn)入該狀態(tài)經(jīng)過一段時間,進(jìn)入半開狀態(tài)。
半開狀態(tài):在半開狀態(tài)期間,允許部分請求通過,在半開期間,觀察失敗狀態(tài)是否超過閥值。如果沒有超過進(jìn)入關(guān)閉狀態(tài),如果超過了進(jìn)入打開狀態(tài)。如此往復(fù)。
之前,查了一些資料,網(wǎng)上所有的資料幾乎都是針對Hystrix的。這個只是針對分布式系統(tǒng)的接口請求,并不能運(yùn)用于我們的系統(tǒng)中,因此這種情況下,根據(jù)原理自己實(shí)現(xiàn)了一個基本的分布式熔斷器,數(shù)值與計數(shù)器存放在redis中,因為redis的操作客戶端不一樣,我就以本地熔斷器為例,講解熔斷器實(shí)現(xiàn)。
希望我的文章能對于理解熔斷器,以及需要熔斷器的人有所幫助。
?
簡單的本地熔斷器實(shí)現(xiàn)
一個基本的本地熔斷器。
對外暴露接口
熔斷器對外暴露接口
/***?熔斷器接口*/ public?interface?CircuitBreaker?{/***?重置熔斷器*/void?reset();/***?是否允許通過熔斷器*/boolean?canPassCheck();/***?統(tǒng)計失敗次數(shù)*/void?countFailNum(); }熔斷器狀態(tài)對外暴露接口
/***?熔斷器狀態(tài)*/ public?interface?CBState?{/***?獲取當(dāng)前狀態(tài)名稱*/String?getStateName();/***?檢查以及校驗當(dāng)前狀態(tài)是否需要扭轉(zhuǎn)*/void?checkAndSwitchState(AbstractCircuitBreaker?cb);/***?是否允許通過熔斷器*/boolean?canPassCheck(AbstractCircuitBreaker?cb);/***?統(tǒng)計失敗次數(shù)*/void?countFailNum(AbstractCircuitBreaker?cb); }三種狀態(tài)
關(guān)閉狀態(tài)實(shí)現(xiàn):
package?com.hirudy.cb.state;import?com.hirudy.cb.cb.AbstractCircuitBreaker; import?java.util.concurrent.atomic.AtomicInteger;/***?User:?Rudy?Tan*?Date:?2018/9/21**?熔斷器-關(guān)閉狀態(tài)*/ public?class?CloseCBState?implements?CBState?{/***?進(jìn)入當(dāng)前狀態(tài)的初始化時間*/private?long?stateTime?=?System.currentTimeMillis();/***?關(guān)閉狀態(tài),失敗計數(shù)器,以及失敗計數(shù)器初始化時間*/private?AtomicInteger?failNum?=?new?AtomicInteger(0);private?long?failNumClearTime?=?System.currentTimeMillis();public?String?getStateName()?{//?獲取當(dāng)前狀態(tài)名稱return?this.getClass().getSimpleName();}public?void?checkAndSwitchState(AbstractCircuitBreaker?cb)?{//?閥值判斷,如果失敗到達(dá)閥值,切換狀態(tài)到打開狀態(tài)long?maxFailNum?=?Long.valueOf(cb.thresholdFailRateForClose.split("/")[0]);if?(failNum.get()?>=?maxFailNum){cb.setState(new?OpenCBState());}}public?boolean?canPassCheck(AbstractCircuitBreaker?cb)?{//?關(guān)閉狀態(tài),請求都應(yīng)該允許通過return?true;}public?void?countFailNum(AbstractCircuitBreaker?cb)?{//?檢查計數(shù)器是否過期了,否則重新計數(shù)long?period?=?Long.valueOf(cb.thresholdFailRateForClose.split("/")[1])?*?1000;long?now?=?System.currentTimeMillis();if?(failNumClearTime?+?period?<=?now){failNum.set(0);}//?失敗計數(shù)failNum.incrementAndGet();//?檢查是否切換狀態(tài)checkAndSwitchState(cb);} }打開狀態(tài)
package?com.hirudy.cb.state;import?com.hirudy.cb.cb.AbstractCircuitBreaker;/***?User:?Rudy?Tan*?Date:?2018/9/21**?熔斷器-打開狀態(tài)*/ public?class?OpenCBState?implements?CBState?{/***?進(jìn)入當(dāng)前狀態(tài)的初始化時間*/private?long?stateTime?=?System.currentTimeMillis();public?String?getStateName()?{//?獲取當(dāng)前狀態(tài)名稱return?this.getClass().getSimpleName();}public?void?checkAndSwitchState(AbstractCircuitBreaker?cb)?{//?打開狀態(tài),檢查等待時間是否已到,如果到了就切換到半開狀態(tài)long?now?=?System.currentTimeMillis();long?idleTime?=?cb.thresholdIdleTimeForOpen?*?1000L;if?(stateTime?+?idleTime?<=?now){cb.setState(new?HalfOpenCBState());}}public?boolean?canPassCheck(AbstractCircuitBreaker?cb)?{//?檢測狀態(tài)checkAndSwitchState(cb);return?false;}public?void?countFailNum(AbstractCircuitBreaker?cb)?{//?nothing} }半開狀態(tài)
package?com.hirudy.cb.state;import?com.hirudy.cb.cb.AbstractCircuitBreaker;import?java.util.concurrent.atomic.AtomicInteger;/***?User:?Rudy?Tan*?Date:?2018/9/21**?熔斷器-半開狀態(tài)*/ public?class?HalfOpenCBState?implements?CBState?{/***?進(jìn)入當(dāng)前狀態(tài)的初始化時間*/private?long?stateTime?=?System.currentTimeMillis();/***?半開狀態(tài),失敗計數(shù)器*/private?AtomicInteger?failNum?=?new?AtomicInteger(0);/***?半開狀態(tài),允許通過的計數(shù)器*/private?AtomicInteger?passNum?=?new?AtomicInteger(0);public?String?getStateName()?{//?獲取當(dāng)前狀態(tài)名稱return?this.getClass().getSimpleName();}public?void?checkAndSwitchState(AbstractCircuitBreaker?cb)?{//?判斷半開時間是否結(jié)束long?idleTime?=?Long.valueOf(cb.thresholdPassRateForHalfOpen.split("/")[1])?*?1000L;long?now?=?System.currentTimeMillis();if?(stateTime?+?idleTime?<=?now){//?如果半開狀態(tài)已結(jié)束,失敗次數(shù)是否超過了閥值int?maxFailNum?=?cb.thresholdFailNumForHalfOpen;if?(failNum.get()?>=?maxFailNum){//?失敗超過閥值,認(rèn)為服務(wù)沒有恢復(fù),重新進(jìn)入熔斷打開狀態(tài)cb.setState(new?OpenCBState());}else?{//?沒超過,認(rèn)為服務(wù)恢復(fù),進(jìn)入熔斷關(guān)閉狀態(tài)cb.setState(new?CloseCBState());}}}public?boolean?canPassCheck(AbstractCircuitBreaker?cb)?{//?檢查是否切換狀態(tài)checkAndSwitchState(cb);//?超過了閥值,不再放量int?maxPassNum?=?Integer.valueOf(cb.thresholdPassRateForHalfOpen.split("/")[0]);if?(passNum.get()?>?maxPassNum){return?false;}//?檢測是否超過了閥值if?(passNum.incrementAndGet()?<=?maxPassNum){return?true;}return?false;}public?void?countFailNum(AbstractCircuitBreaker?cb)?{//?失敗計數(shù)failNum.incrementAndGet();//?檢查是否切換狀態(tài)checkAndSwitchState(cb);} }熔斷器
抽象熔斷器
package?com.hirudy.cb.cb;import?com.hirudy.cb.state.CBState; import?com.hirudy.cb.state.CloseCBState;/***?User:?Rudy?Tan*?Date:?2018/9/21**?基礎(chǔ)熔斷器*/ public?abstract?class?AbstractCircuitBreaker?implements?CircuitBreaker?{/***?熔斷器當(dāng)前狀態(tài)*/private?volatile?CBState?state?=?new?CloseCBState();/***?在熔斷器關(guān)閉的情況下,在多少秒內(nèi)失敗多少次進(jìn)入,熔斷打開狀態(tài)(默認(rèn)10分鐘內(nèi),失敗10次進(jìn)入打開狀態(tài))*/public?String?thresholdFailRateForClose?=?"10/600";/***?在熔斷器打開的情況下,熔斷多少秒進(jìn)入半開狀態(tài),(默認(rèn)熔斷30分鐘)*/public?int?thresholdIdleTimeForOpen?=?1800;/***?在熔斷器半開的情況下,?在多少秒內(nèi)放多少次請求,去試探(默認(rèn)10分鐘內(nèi),放10次請求)*/public?String?thresholdPassRateForHalfOpen?=?"10/600";/***?在熔斷器半開的情況下,?試探期間,如果有超過多少次失敗的,重新進(jìn)入熔斷打開狀態(tài),否者進(jìn)入熔斷關(guān)閉狀態(tài)。*/public?int?thresholdFailNumForHalfOpen?=?1;public?CBState?getState()?{return?state;}public?void?setState(CBState?state)?{//?當(dāng)前狀態(tài)不能切換為當(dāng)前狀態(tài)CBState?currentState?=?getState();if?(currentState.getStateName().equals(state.getStateName())){return;}//?多線程環(huán)境加鎖synchronized?(this){//?二次判斷currentState?=?getState();if?(currentState.getStateName().equals(state.getStateName())){return;}//?更新狀態(tài)this.state?=?state;System.out.println("熔斷器狀態(tài)轉(zhuǎn)移:"?+?currentState.getStateName()?+?"->"?+?state.getStateName());}} }本地熔斷器
package?com.hirudy.cb.cb;import?com.hirudy.cb.state.CloseCBState;/***?User:?Rudy?Tan*?Date:?2018/9/22**?本地熔斷器(把它當(dāng)成了工廠了)*/ public?class?LocalCircuitBreaker?extends?AbstractCircuitBreaker?{public?LocalCircuitBreaker(String?failRateForClose,int?idleTimeForOpen,String?passRateForHalfOpen,?int?failNumForHalfOpen){this.thresholdFailRateForClose?=?failRateForClose;this.thresholdIdleTimeForOpen?=?idleTimeForOpen;this.thresholdPassRateForHalfOpen?=?passRateForHalfOpen;this.thresholdFailNumForHalfOpen?=?failNumForHalfOpen;}public?void?reset()?{this.setState(new?CloseCBState());}public?boolean?canPassCheck()?{return?getState().canPassCheck(this);}public?void?countFailNum()?{getState().countFailNum(this);} }測試?yán)?/p> import?com.hirudy.cb.cb.CircuitBreaker; import?com.hirudy.cb.cb.LocalCircuitBreaker;import?java.util.Random; import?java.util.concurrent.CountDownLatch;/***?User:?Rudy?Tan*?Date:?2018/8/27*/ public?class?App?{public?static?void?main(String[]?args)?throws?InterruptedException?{final?int?maxNum?=?200;final?CountDownLatch?countDownLatch?=?new?CountDownLatch(maxNum);final?CircuitBreaker?circuitBreaker?=?new?LocalCircuitBreaker("5/20",?10,?"5/10",?2);for?(int?i=0;?i?<?maxNum;?i++){new?Thread(new?Runnable()?{public?void?run()?{//?模擬隨機(jī)請求try?{Thread.sleep(new?Random().nextInt(20)?*?1000);}?catch?(InterruptedException?e)?{e.printStackTrace();}try{//?過熔斷器if?(circuitBreaker.canPassCheck()){//?do?somethingSystem.out.println("正常業(yè)務(wù)邏輯操作");//?模擬后期的服務(wù)恢復(fù)狀態(tài)if?(countDownLatch.getCount()?>=?maxNum/2){//?模擬隨機(jī)失敗if?(new?Random().nextInt(2)?==?1){throw?new?Exception("mock?error");}}}?else?{System.out.println("攔截業(yè)務(wù)邏輯操作");}}catch?(Exception?e){System.out.println("業(yè)務(wù)執(zhí)行失敗了");//?熔斷器計數(shù)器circuitBreaker.countFailNum();}countDownLatch.countDown();}}).start();//?模擬隨機(jī)請求try?{Thread.sleep(new?Random().nextInt(5)?*?100);}?catch?(InterruptedException?e)?{e.printStackTrace();}}countDownLatch.await();System.out.println("end");} }
結(jié)果
總結(jié)
以上是生活随笔為你收集整理的线上防雪崩利器——熔断器设计原理与实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 9个最佳摄影标识以及如何免费获得[202
- 下一篇: 业务太复杂?教你如何降低软件的复杂性