當服務有較多外部依賴時,若是其中某個服務的不可用,致使整個集羣會受到影響(好比超時,致使大量的請求被阻塞,從而致使外部請求沒法進來),這種狀況下采用hystrix就頗有用了java
出於這個目的,瞭解了下hystrix框架,下面記錄下,框架嘗新的歷程git
經過官網和相關博文,能夠簡單的說一下這個工做機制,大體流程以下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
信號量方式 : 本線程執行,無異步,無超時,支持限流,消耗更小框架
基本上有上面這個簡單的概念以後,開始進入咱們的使用測試流程異步
<dependency> <groupId>com.netflix.hystrix</groupId> <artifactId>hystrix-core</artifactId> <version>1.5.12</version> </dependency>
從官方文檔來看,支持兩種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方法是核心執行服務調用,若是須要某些服務不統計到熔斷的失敗率(好比由於調用姿式不對致使服務內部的異常拋上來了,可是服務自己是正常的),這個時候,就須要包裝下調用邏輯,將不須要的異常包裝到 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; } } }
當發生失敗時,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()
hystrix本身提供了一套監控插件,基本上你們都會有本身的監控統計信息,所以須要對這個數據進行和自定義,目前還沒想到能夠如何優雅的處理這些統計信息
主要是看了下這個東西能夠怎麼玩,整個用下來的感受就是,設計的比較有意思,可是配置參數太多,不少都沒有徹底摸透
其次就是一些特殊的case(如監控,報警,特殊狀況過濾)須要處理時,用起來並非很順手,主要問題仍是沒有理解清楚這個框架的內部工做機制的問題
基於hexo + github pages搭建的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛
盡信書則不如,以上內容,純屬一家之言,因本人能力通常,見識有限,如發現bug或者有更好的建議,隨時歡迎批評指正