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