Hystrix熔斷器技術(shù)解析-HystrixCircuitBreaker

一传黄、熔斷器(Circuit Breaker)介紹

熔斷器突想,現(xiàn)實生活中有一個很好的類比,就是家庭電路中都會安裝一個保險盒荚藻,當(dāng)電流過大的時候保險盒里面的保險絲會自動斷掉,來保護家里的各種電器及電路洁段。Hystrix中的熔斷器(Circuit Breaker)也是起到這樣的作用应狱,Hystrix在運行過程中會向每個commandKey對應(yīng)的熔斷器報告成功、失敗眉撵、超時和拒絕的狀態(tài)侦香,熔斷器維護計算統(tǒng)計的數(shù)據(jù),根據(jù)這些統(tǒng)計的信息來確定熔斷器是否打開纽疟。如果打開罐韩,后續(xù)的請求都會被截斷。然后會隔一段時間默認(rèn)是5s污朽,嘗試半開散吵,放入一部分流量請求進(jìn)來,相當(dāng)于對依賴服務(wù)進(jìn)行一次健康檢查,如果恢復(fù)矾睦,熔斷器關(guān)閉晦款,隨后完全恢復(fù)調(diào)用。如下圖:

熔斷器開關(guān)圖.png

說明枚冗,上面說的commandKey缓溅,就是在初始化的時候設(shè)置的andCommandKey(HystrixCommandKey.Factory.asKey("testCommandKey"))

再來看下熔斷器在整個Hystrix流程圖中的位置,從步驟4開始赁温,如下圖:

Hystrix流程圖.png

Hystrix會檢查Circuit Breaker的狀態(tài)坛怪。如果Circuit Breaker的狀態(tài)為開啟狀態(tài),Hystrix將不會執(zhí)行對應(yīng)指令股囊,而是直接進(jìn)入失敗處理狀態(tài)(圖中8 Fallback)袜匿。如果Circuit Breaker的狀態(tài)為關(guān)閉狀態(tài),Hystrix會繼續(xù)進(jìn)行線程池稚疹、任務(wù)隊列居灯、信號量的檢查(圖中5)

二、如何使用熔斷器(Circuit Breaker)

由于Hystrix是一個容錯框架内狗,因此我們在使用的時候怪嫌,要達(dá)到熔斷的目的只需配置一些參數(shù)就可以了。但我們要達(dá)到真正的效果其屏,就必須要了解這些參數(shù)喇勋。Circuit Breaker一共包括如下6個參數(shù)。
1偎行、circuitBreaker.enabled
是否啟用熔斷器川背,默認(rèn)是TURE。
2蛤袒、circuitBreaker.forceOpen
熔斷器強制打開熄云,始終保持打開狀態(tài)。默認(rèn)值FLASE妙真。
3缴允、circuitBreaker.forceClosed
熔斷器強制關(guān)閉,始終保持關(guān)閉狀態(tài)珍德。默認(rèn)值FLASE练般。
4、circuitBreaker.errorThresholdPercentage
設(shè)定錯誤百分比锈候,默認(rèn)值50%薄料,例如一段時間(10s)內(nèi)有100個請求,其中有55個超時或者異常返回了泵琳,那么這段時間內(nèi)的錯誤百分比是55%摄职,大于了默認(rèn)值50%誊役,這種情況下觸發(fā)熔斷器-打開。
5谷市、circuitBreaker.requestVolumeThreshold
默認(rèn)值20.意思是至少有20個請求才進(jìn)行errorThresholdPercentage錯誤百分比計算蛔垢。比如一段時間(10s)內(nèi)有19個請求全部失敗了。錯誤百分比是100%迫悠,但熔斷器不會打開鹏漆,因為requestVolumeThreshold的值是20. 這個參數(shù)非常重要,熔斷器是否打開首先要滿足這個條件及皂,源代碼如下

// check if we are past the statisticalWindowVolumeThreshold
if (health.getTotalRequests() < properties.circuitBreakerRequestVolumeThreshold().get()) {
    // we are not past the minimum volume threshold for the statisticalWindow so we'll return false immediately and not calculate anything
    return false;
}

if (health.getErrorPercentage() < properties.circuitBreakerErrorThresholdPercentage().get()) {
    return false;
}

6甫男、circuitBreaker.sleepWindowInMilliseconds
半開試探休眠時間,默認(rèn)值5000ms验烧。當(dāng)熔斷器開啟一段時間之后比如5000ms,會嘗試放過去一部分流量進(jìn)行試探又跛,確定依賴服務(wù)是否恢復(fù)碍拆。

測試代碼(模擬10次調(diào)用,錯誤百分比為5%的情況下慨蓝,打開熔斷器開關(guān)感混。):

package myHystrix.threadpool;

import com.netflix.hystrix.*;
import org.junit.Test;

import java.util.Random;

/**
 * Created by wangxindong on 2017/8/15.
 */
public class GetOrderCircuitBreakerCommand extends HystrixCommand<String> {

    public GetOrderCircuitBreakerCommand(String name){
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ThreadPoolTestGroup"))
                .andCommandKey(HystrixCommandKey.Factory.asKey("testCommandKey"))
                .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(name))
                .andCommandPropertiesDefaults(
                        HystrixCommandProperties.Setter()
                                .withCircuitBreakerEnabled(true)//默認(rèn)是true,本例中為了展現(xiàn)該參數(shù)
                                .withCircuitBreakerForceOpen(false)//默認(rèn)是false礼烈,本例中為了展現(xiàn)該參數(shù)
                                .withCircuitBreakerForceClosed(false)//默認(rèn)是false弧满,本例中為了展現(xiàn)該參數(shù)
                                .withCircuitBreakerErrorThresholdPercentage(5)//(1)錯誤百分比超過5%
                                .withCircuitBreakerRequestVolumeThreshold(10)//(2)10s以內(nèi)調(diào)用次數(shù)10次,同時滿足(1)(2)熔斷器打開
                                .withCircuitBreakerSleepWindowInMilliseconds(5000)//隔5s之后此熬,熔斷器會嘗試半開(關(guān)閉)庭呜,重新放進(jìn)來請求
//                                .withExecutionTimeoutInMilliseconds(1000)
                )
                .andThreadPoolPropertiesDefaults(
                        HystrixThreadPoolProperties.Setter()
                                .withMaxQueueSize(10)   //配置隊列大小
                                .withCoreSize(2)    // 配置線程池里的線程數(shù)
                )
        );
    }

    @Override
    protected String run() throws Exception {
        Random rand = new Random();
        //模擬錯誤百分比(方式比較粗魯?shù)梢宰C明問題)
        if(1==rand.nextInt(2)){
//            System.out.println("make exception");
            throw new Exception("make exception");
        }
        return "running:  ";
    }

    @Override
    protected String getFallback() {
//        System.out.println("FAILBACK");
        return "fallback: ";
    }

    public static class UnitTest{

        @Test
        public void testCircuitBreaker() throws Exception{
            for(int i=0;i<25;i++){
                Thread.sleep(500);
                HystrixCommand<String> command = new GetOrderCircuitBreakerCommand("testCircuitBreaker");
                String result = command.execute();
                //本例子中從第11次,熔斷器開始打開
                System.out.println("call times:"+(i+1)+"   result:"+result +" isCircuitBreakerOpen: "+command.isCircuitBreakerOpen());
                //本例子中5s以后犀忱,熔斷器嘗試關(guān)閉募谎,放開新的請求進(jìn)來
            }
        }
    }
}

測試結(jié)果:

call times:1 result:fallback: isCircuitBreakerOpen: false
call times:2 result:running: isCircuitBreakerOpen: false
call times:3 result:running: isCircuitBreakerOpen: false
call times:4 result:fallback: isCircuitBreakerOpen: false
call times:5 result:running: isCircuitBreakerOpen: false
call times:6 result:fallback: isCircuitBreakerOpen: false
call times:7 result:fallback: isCircuitBreakerOpen: false
call times:8 result:fallback: isCircuitBreakerOpen: false
call times:9 result:fallback: isCircuitBreakerOpen: false
call times:10 result:fallback: isCircuitBreakerOpen: false
熔斷器打開
call times:11 result:fallback: isCircuitBreakerOpen: true
call times:12 result:fallback: isCircuitBreakerOpen: true
call times:13 result:fallback: isCircuitBreakerOpen: true
call times:14 result:fallback: isCircuitBreakerOpen: true
call times:15 result:fallback: isCircuitBreakerOpen: true
call times:16 result:fallback: isCircuitBreakerOpen: true
call times:17 result:fallback: isCircuitBreakerOpen: true
call times:18 result:fallback: isCircuitBreakerOpen: true
call times:19 result:fallback: isCircuitBreakerOpen: true
call times:20 result:fallback: isCircuitBreakerOpen: true
5s后熔斷器關(guān)閉
call times:21 result:running: isCircuitBreakerOpen: false
call times:22 result:running: isCircuitBreakerOpen: false
call times:23 result:fallback: isCircuitBreakerOpen: false
call times:24 result:running: isCircuitBreakerOpen: false
call times:25 result:running: isCircuitBreakerOpen: false

三、熔斷器(Circuit Breaker)源代碼HystrixCircuitBreaker.java分析
HystrixCircuitBreaker.java.png

Factory 是一個工廠類阴汇,提供HystrixCircuitBreaker實例

public static class Factory {
        //用一個ConcurrentHashMap來保存HystrixCircuitBreaker對象
        private static ConcurrentHashMap<String, HystrixCircuitBreaker> circuitBreakersByCommand = new ConcurrentHashMap<String, HystrixCircuitBreaker>();
        
//Hystrix首先會檢查ConcurrentHashMap中有沒有對應(yīng)的緩存的斷路器数冬,如果有的話直接返回。如果沒有的話就會新創(chuàng)建一個HystrixCircuitBreaker實例搀庶,將其添加到緩存中并且返回
        public static HystrixCircuitBreaker getInstance(HystrixCommandKey key, HystrixCommandGroupKey group, HystrixCommandProperties properties, HystrixCommandMetrics metrics) {
            
            HystrixCircuitBreaker previouslyCached = circuitBreakersByCommand.get(key.name());
            if (previouslyCached != null) {
                return previouslyCached;
            }

            
            HystrixCircuitBreaker cbForCommand = circuitBreakersByCommand.putIfAbsent(key.name(), new HystrixCircuitBreakerImpl(key, group, properties, metrics));
            if (cbForCommand == null) {
                return circuitBreakersByCommand.get(key.name());
            } else {
                return cbForCommand;
            }
        }

        
        public static HystrixCircuitBreaker getInstance(HystrixCommandKey key) {
            return circuitBreakersByCommand.get(key.name());
        }

        static void reset() {
            circuitBreakersByCommand.clear();
        }
}

HystrixCircuitBreakerImpl是HystrixCircuitBreaker的實現(xiàn)拐纱,allowRequest()、isOpen()哥倔、markSuccess()都會在HystrixCircuitBreakerImpl有默認(rèn)的實現(xiàn)秸架。

static class HystrixCircuitBreakerImpl implements HystrixCircuitBreaker {
        private final HystrixCommandProperties properties;
        private final HystrixCommandMetrics metrics;

        /* 變量circuitOpen來代表斷路器的狀態(tài),默認(rèn)是關(guān)閉 */
        private AtomicBoolean circuitOpen = new AtomicBoolean(false);

        /* 變量circuitOpenedOrLastTestedTime記錄著斷路恢復(fù)計時器的初始時間未斑,用于Open狀態(tài)向Close狀態(tài)的轉(zhuǎn)換 */
        private AtomicLong circuitOpenedOrLastTestedTime = new AtomicLong();

        protected HystrixCircuitBreakerImpl(HystrixCommandKey key, HystrixCommandGroupKey commandGroup, HystrixCommandProperties properties, HystrixCommandMetrics metrics) {
            this.properties = properties;
            this.metrics = metrics;
        }

        /*用于關(guān)閉熔斷器并重置統(tǒng)計數(shù)據(jù)*/
        public void markSuccess() {
            if (circuitOpen.get()) {
                if (circuitOpen.compareAndSet(true, false)) {
                    //win the thread race to reset metrics
                    //Unsubscribe from the current stream to reset the health counts stream.  This only affects the health counts view,
                    //and all other metric consumers are unaffected by the reset
                    metrics.resetStream();
                }
            }
        }

        @Override
        public boolean allowRequest() {
            //是否設(shè)置強制開啟
            if (properties.circuitBreakerForceOpen().get()) {
                return false;
            }
            if (properties.circuitBreakerForceClosed().get()) {//是否設(shè)置強制關(guān)閉
                isOpen();
                // properties have asked us to ignore errors so we will ignore the results of isOpen and just allow all traffic through
                return true;
            }
            return !isOpen() || allowSingleTest();
        }

        public boolean allowSingleTest() {
            long timeCircuitOpenedOrWasLastTested = circuitOpenedOrLastTestedTime.get();
            //獲取熔斷恢復(fù)計時器記錄的初始時間circuitOpenedOrLastTestedTime咕宿,然后判斷以下兩個條件是否同時滿足:
            // 1) 熔斷器的狀態(tài)為開啟狀態(tài)(circuitOpen.get() == true)
            // 2) 當(dāng)前時間與計時器初始時間之差大于計時器閾值circuitBreakerSleepWindowInMilliseconds(默認(rèn)為 5 秒)
            //如果同時滿足的話币绩,表示可以從Open狀態(tài)向Close狀態(tài)轉(zhuǎn)換。Hystrix會通過CAS操作將circuitOpenedOrLastTestedTime設(shè)為當(dāng)前時間府阀,并返回true缆镣。如果不同時滿足,返回false试浙,代表熔斷器關(guān)閉或者計時器時間未到董瞻。
            if (circuitOpen.get() && System.currentTimeMillis() > timeCircuitOpenedOrWasLastTested + properties.circuitBreakerSleepWindowInMilliseconds().get()) {
                // We push the 'circuitOpenedTime' ahead by 'sleepWindow' since we have allowed one request to try.
                // If it succeeds the circuit will be closed, otherwise another singleTest will be allowed at the end of the 'sleepWindow'.
                if (circuitOpenedOrLastTestedTime.compareAndSet(timeCircuitOpenedOrWasLastTested, System.currentTimeMillis())) {
                    // if this returns true that means we set the time so we'll return true to allow the singleTest
                    // if it returned false it means another thread raced us and allowed the singleTest before we did
                    return true;
                }
            }
            return false;
        }

        @Override
        public boolean isOpen() {
            if (circuitOpen.get()) {//獲取斷路器的狀態(tài)
                // if we're open we immediately return true and don't bother attempting to 'close' ourself as that is left to allowSingleTest and a subsequent successful test to close
                return true;
            }

            // Metrics數(shù)據(jù)中獲取HealthCounts對象
            HealthCounts health = metrics.getHealthCounts();

            // 檢查對應(yīng)的請求總數(shù)(totalCount)是否小于屬性中的請求容量閾值circuitBreakerRequestVolumeThreshold,默認(rèn)20,如果是的話表示熔斷器可以保持關(guān)閉狀態(tài)田巴,返回false
            if (health.getTotalRequests() < properties.circuitBreakerRequestVolumeThreshold().get()) {
                
                return false;
            }

            //不滿足請求總數(shù)條件钠糊,就再檢查錯誤比率(errorPercentage)是否小于屬性中的錯誤百分比閾值(circuitBreakerErrorThresholdPercentage,默認(rèn) 50),如果是的話表示斷路器可以保持關(guān)閉狀態(tài)壹哺,返回 false
            if (health.getErrorPercentage() < properties.circuitBreakerErrorThresholdPercentage().get()) {
                return false;
            } else {
                // 如果超過閾值抄伍,Hystrix會判定服務(wù)的某些地方出現(xiàn)了問題,因此通過CAS操作將斷路器設(shè)為開啟狀態(tài)管宵,并記錄此時的系統(tǒng)時間作為定時器初始時間截珍,最后返回 true
                if (circuitOpen.compareAndSet(false, true)) {
                    circuitOpenedOrLastTestedTime.set(System.currentTimeMillis());
                    return true;
                } else {
                    return true;
                }
            }
        }

    }
四、總結(jié)

每個熔斷器默認(rèn)維護10個bucket,每秒一個bucket,每個blucket記錄成功,失敗,超時,拒絕的狀態(tài)箩朴,默認(rèn)錯誤超過50%且10秒內(nèi)超過20個請求進(jìn)行中斷攔截岗喉。下圖顯示HystrixCommand或HystrixObservableCommand如何與HystrixCircuitBreaker及其邏輯和決策流程進(jìn)行交互,包括計數(shù)器在斷路器中的行為炸庞。

熔斷器流程交互圖.png

轉(zhuǎn)載請注明出處钱床,并附上鏈接http://www.reibang.com/p/14958039fd15
參考資料:https://github.com/Netflix/Hystrix/wiki

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市埠居,隨后出現(xiàn)的幾起案子查牌,更是在濱河造成了極大的恐慌,老刑警劉巖拐格,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件僧免,死亡現(xiàn)場離奇詭異,居然都是意外死亡捏浊,警方通過查閱死者的電腦和手機懂衩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來金踪,“玉大人浊洞,你說我怎么就攤上這事『恚” “怎么了法希?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長靶瘸。 經(jīng)常有香客問我苫亦,道長毛肋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任屋剑,我火速辦了婚禮润匙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘唉匾。我一直安慰自己孕讳,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布巍膘。 她就那樣靜靜地躺著厂财,像睡著了一般。 火紅的嫁衣襯著肌膚如雪峡懈。 梳的紋絲不亂的頭發(fā)上璃饱,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天,我揣著相機與錄音逮诲,去河邊找鬼帜平。 笑死,一個胖子當(dāng)著我的面吹牛梅鹦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播冗锁,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼齐唆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了冻河?” 一聲冷哼從身側(cè)響起箍邮,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎叨叙,沒想到半個月后锭弊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡擂错,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年味滞,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钮呀。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡剑鞍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出爽醋,到底是詐尸還是另有隱情蚁署,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布蚂四,位于F島的核電站光戈,受9級特大地震影響哪痰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜久妆,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一晌杰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧镇饺,春花似錦乎莉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至监右,卻和暖如春边灭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背健盒。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工绒瘦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人扣癣。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓惰帽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親父虑。 傳聞我的和親對象是個殘疾皇子该酗,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容