熔斷Hystrix使用嚐鮮

熔斷Hystrix使用嚐鮮

當服務有較多外部依賴時,若是其中某個服務的不可用,致使整個集羣會受到影響(好比超時,致使大量的請求被阻塞,從而致使外部請求沒法進來),這種狀況下采用hystrix就頗有用了java

出於這個目的,瞭解了下hystrix框架,下面記錄下,框架嘗新的歷程git

I. 原理探究

經過官網和相關博文,能夠簡單的說一下這個工做機制,大體流程以下github

首先是請求過來 -> 判斷熔斷器是否開 -> 服務調用 -> 異常則走fallback,失敗計數+1 -> 結束redis

下面是主流程圖多線程

流程圖

graph LR
    A(請求)-->B{熔斷器是否已開}
    B --> | 熔斷 | D[fallback邏輯]
    B --> | 未熔斷 | E[線程池/Semphore]
    E --> F{線程池滿/無可用信號量}
    F --> | yes | D
    F --> | no | G{建立線程執行/本線程運行}
    G --> | yes | I(結束)
    G --> | no | D
    D --> I(結束)

熔斷機制主要提供了兩種,一個是基於線程池的隔離方式來作;還有一個則是根據信號量的搶佔來作hexo

線程池方式 : 支持異步,支持超時設置,支持限流app

信號量方式 : 本線程執行,無異步,無超時,支持限流,消耗更小框架

基本上有上面這個簡單的概念以後,開始進入咱們的使用測試流程異步

II. 使用嚐鮮

1. 引入依賴

<dependency>
    <groupId>com.netflix.hystrix</groupId>
    <artifactId>hystrix-core</artifactId>
    <version>1.5.12</version>
</dependency>

2. 簡單使用

從官方文檔來看,支持兩種Command方式,一個是基於觀察者模式的ObserverCommand, 一個是基本的Command,先用簡單的看如下ide

public class HystrixConfigTest extends HystrixCommand<String> {

    private final String name;

    public HystrixConfigTest(String name, boolean ans) {
//        注意的是同一個任務,
        super(Setter.withGroupKey(
//                CommandGroup是每一個命令最少配置的必選參數,在不指定ThreadPoolKey的狀況下,字面值用於對不一樣依賴的線程池/信號區分
                HystrixCommandGroupKey.Factory.asKey("CircuitBreakerTestGroup"))
//                每一個CommandKey表明一個依賴抽象,相同的依賴要使用相同的CommandKey名稱。依賴隔離的根本就是對相同CommandKey的依賴作隔離.
                        .andCommandKey(HystrixCommandKey.Factory.asKey("CircuitBreakerTestKey_" + ans))
//                當對同一業務依賴作隔離時使用CommandGroup作區分,可是對同一依賴的不一樣遠程調用如(一個是redis 一個是http),可使用HystrixThreadPoolKey作隔離區分
                        .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("CircuitBreakerTest_" + ans))
                        .andThreadPoolPropertiesDefaults(    // 配置線程池
                                HystrixThreadPoolProperties.Setter()
                                        .withCoreSize(12)    // 配置線程池裏的線程數,設置足夠多線程,以防未熔斷卻打滿threadpool
                        )
                        .andCommandPropertiesDefaults(    // 配置熔斷器
                                HystrixCommandProperties.Setter()
                                        .withCircuitBreakerEnabled(true)
                                        .withCircuitBreakerRequestVolumeThreshold(3)
                                        .withCircuitBreakerErrorThresholdPercentage(80)
//                		.withCircuitBreakerForceOpen(true)	// 置爲true時,全部請求都將被拒絕,直接到fallback
//                		.withCircuitBreakerForceClosed(true)	// 置爲true時,將忽略錯誤
//                                        .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)    // 信號量隔離
                                        .withExecutionIsolationSemaphoreMaxConcurrentRequests(20)
                                        .withExecutionTimeoutEnabled(true)
                                        .withExecutionTimeoutInMilliseconds(200)
                                .withCircuitBreakerSleepWindowInMilliseconds(1000) //熔斷器打開到關閉的時間窗長度
//                		.withExecutionTimeoutInMilliseconds(5000)
                        )
        );
        this.name = name;
    }

    @Override
    protected String run() throws Exception {
        System.out.println("running run():" + name + " thread: " + Thread.currentThread().getName());
        int num = Integer.valueOf(name);
        if (num % 2 == 0 && num < 10) {    // 直接返回
            return name;
        } else if (num < 40) {
            Thread.sleep(300);
            return "sleep+"+ name;
        } else {    // 無限循環模擬超時
            return name;
        }
    }
//
//    @Override
//    protected String getFallback() {
//        Throwable t = this.getExecutionException();
//        if(t instanceof HystrixRuntimeException) {
//            System.out.println(Thread.currentThread() + " --> " + ((HystrixRuntimeException) t).getFailureType());
//        } else if (t instanceof HystrixTimeoutException) {
//            System.out.println(t.getCause());
//        } else {
//            t.printStackTrace();
//        }
//        System.out.println(Thread.currentThread() + " --> ----------over------------");
//        return "CircuitBreaker fallback: " + name;
//    }

    public static class UnitTest {

        @Test
        public void testSynchronous() throws IOException, InterruptedException {
            for (int i = 0; i < 50; i++) {
                if (i == 41) {
                    Thread.sleep(2000);
                }
                try {
                    System.out.println("===========" + new HystrixConfigTest(String.valueOf(i), i % 2 == 0).execute());
                } catch (HystrixRuntimeException e) {
                    System.out.println(i + " : " + e.getFailureType() + " >>>> " + e.getCause() + " <<<<<");
                } catch (Exception e) {
                    System.out.println("run()拋出HystrixBadRequestException時,被捕獲到這裏" + e.getCause());
                }
            }

            System.out.println("------開始打印現有線程---------");
            Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
            for (Thread thread : map.keySet()) {
                System.out.println("--->name-->" + thread.getName());
            }
            System.out.println("thread num: " + map.size());

            System.in.read();
        }
    }
}

使用起來仍是比較簡單的,通常步驟以下:

  • 繼承 HsytrixCommand
  • 重載構造方法,內部須要指定各類配置
  • 實現run方法,這個裏面主要執行熔斷監控的方法

寫上面的代碼比較簡單,可是有幾個地方不太好處理

  1. 配置項的具體含義,又是怎麼生效的?
  2. 某些異常不進入熔斷邏輯怎麼辦?
  3. 監控數據如何獲取?
  4. 如何模擬各類不一樣的case(超時?服務異常?熔斷已開啓?線程池滿?無可用信號量?半熔斷的重試?)

3. 實測理解

根據上面那一段代碼的刪刪改改,貌似理解了如下幾個點,不知道對誤

a. 配置相關

  • groupKey 用於區分線程池和信號量,即一個group對應一個
  • commandKey 很重要,這個是用於區分業務
    • 簡單來說,group相似提供服務的app,command則對應app提供的service,一個app能夠有多個service,這裏就是將一個app的全部請求都放在一個線程池(or共享一個信號量)
  • 開啓熔斷機制,指定觸發熔斷的最小請求數(10s內),指定打開熔斷的條件(失敗率)
  • 設置熔斷策略(線程池or信號量)
  • 設置重試時間(默認熔斷開啓後5s,放幾個請求進去,看服務是否恢復)
  • 設置線程池大小,設置信號量大小,設置隊列大小
  • 設置超時時間,設置容許超時設置

b. 使用相關

run方法是核心執行服務調用,若是須要某些服務不統計到熔斷的失敗率(好比由於調用姿式不對致使服務內部的異常拋上來了,可是服務自己是正常的),這個時候,就須要包裝下調用邏輯,將不須要的異常包裝到 HystrixBadRequestException 類裏

@Override
protected String run() {
    try {
        return func.apply(route, parameterDescs);
    } catch (Exception e) {
        if (exceptionExcept(e)) {
            // 若是是不關注的異常case, 不進入熔斷邏輯
            throw new HystrixBadRequestException("unexpected exception!", e);
        } else {
            throw e;
        }
    }
}

c. 如何獲取失敗的緣由

當發生失敗時,hystrix會把原生的異常包裝到 HystrixRuntimeException 這個類裏,因此咱們能夠在調用的地方以下處理

try {
    System.out.println("===========" + new HystrixConfigTest(String.valueOf(i), i % 2 == 0).execute());
} catch (HystrixRuntimeException e) {
    System.out.println(i + " : " + e.getFailureType() + " >>>> " + e.getCause() + " <<<<<");
} catch (Exception e) {
    System.out.println("run()拋出HystrixBadRequestException時,被捕獲到這裏" + e.getCause());
}

當定義了fallback邏輯時,異常則不會拋到具體的調用方,因此在 fallback 方法內,則有必要獲取對應的異常信息

// 獲取異常信息
Throwable t = this.getExecutionException();

而後下一步就是須要獲取對應的異常緣由了,經過FailureType來代表失敗的根源

((HystrixRuntimeException) t).getFailureType()

d.如何獲取統計信息

hystrix本身提供了一套監控插件,基本上你們都會有本身的監控統計信息,所以須要對這個數據進行和自定義,目前還沒想到能夠如何優雅的處理這些統計信息

4. 小結

主要是看了下這個東西能夠怎麼玩,整個用下來的感受就是,設計的比較有意思,可是配置參數太多,不少都沒有徹底摸透

其次就是一些特殊的case(如監控,報警,特殊狀況過濾)須要處理時,用起來並非很順手,主要問題仍是沒有理解清楚這個框架的內部工做機制的問題

III. 其餘

我的博客: Z+|blog

基於hexo + github pages搭建的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛

聲明

盡信書則不如,以上內容,純屬一家之言,因本人能力通常,見識有限,如發現bug或者有更好的建議,隨時歡迎批評指正

掃描關注

QrCode

相關文章
相關標籤/搜索