1.依賴隔離概述java
依賴隔離是Hystrix的核心目的。依賴隔離其實就是資源隔離,把對依賴使用的資源隔離起來,統一控制和調度。那爲何須要把資源隔離起來呢?主要有如下幾點:編程
1.合理分配資源,把給資源分配的控制權交給用戶,某一個依賴的故障不會影響到其餘的依賴調用,訪問資源也不受影響。緩存
2.能夠方便的指定調用策略,好比超時異常,熔斷處理。多線程
3.對依賴限制資源也是對下游依賴起到一個保護做用,避免大量的併發請求在依賴服務有問題的時候形成依賴服務癱瘓或者更糟的雪崩效應。架構
4.對依賴調用進行封裝有利於對調用的監控和分析,相似於hystrix-dashboard的使用。併發
Hystrix提供了兩種依賴隔離方式:線程池隔離 和 信號量隔離。以下圖,線程池隔離,Hystrix能夠爲每個依賴創建一個線程池,使之和其餘依賴的使用資源隔離,同時限制他們的併發訪問和阻塞擴張。每一個依賴能夠根據權重分配資源(這裏主要是線程),每一部分的依賴出現了問題,也不會影響其餘依賴的使用資源。異步
2.線程池隔離異步編程
若是簡單的使用異步線程來實現依賴調用會有以下問題:一、線程的建立和銷燬;二、線程上下文空間的切換,用戶態和內核態的切換帶來的性能損耗。函數
使用線程池的方式能夠解決第一種問題,可是第二個問題計算開銷是不能避免的。Netflix在使用過程當中詳細評估了使用異步線程和同步線程帶來的性能差別,結果代表在99%的狀況下,異步線程帶來的幾毫秒延遲的徹底能夠接受的。性能
3.線程池隔離的優缺點
優勢:
缺點:
4.Command Name&Command Group
Hystrix使用Command模式對依賴調用進行封裝。當咱們寫一個調用繼承HystrixCommand的時候,能夠指定一個名稱Command Name。若是不指定Hystrix將會使用getClass().getSimpleName()來默認獲取。若是要指定,可使用以下代碼,使用HystrixCommandKey.Factory幫助類在構造函數中指定。
public HelloWorldCommand(String name) { //定義命令組 和 方法調用超時時間 super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("HelloWorldGroup")) .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorldCommand"))); this.name = name; }
而Command Group能夠把一組Command歸爲一組。如上例代碼,可使用HystrixCommandGroupKey.Factory.asKey來指定Command Group。通常狀況下,邏輯上是同一類型的會放在同一個Command Group中。好比,獲取用戶相關信息的依賴能夠放在一塊兒。
雖然Hystrix能夠爲每一個依賴創建一個線程池,可是若是依賴成千上萬,創建那麼多線程池確定是不可能的。因此默認狀況下,Hystrix會爲每個Command Group創建一個線程池。
5.Command Thread Pool
Hystrix能夠指定建立或關聯上一個線程池,每個線程池都有一個Key。這個線程池就是線程隔離的關鍵,全部的監控、緩存、調用等等都來自於這個線程池。能夠經過以下代碼指定線程池:
public HelloWorldCommand(String name) { //定義命令組 和 方法調用超時時間 super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("HelloWorldGroup")) .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorldCommand")) .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("HelloWorldPool"))); this.name = name; }
上文說到,默認狀況下,每個Command Group會自動建立一個線程池。那何時咱們須要單獨指定線程池呢?由於線程池主要的目的是隔離,因此當有一些依賴在一個Command Group中,可是又有隔離的必要的時候,好比一個依賴的超時會用滿全部的線程池線程,而不該該影響其餘的依賴。
6.基本實現原理
Command模式:Hystrix中大量使用rxjava來實現Command模式。全部自定義的Command,無論繼承於HystrixObservableCommand仍是HystrixCommand,最終都繼承於AbstractCommand。Thread Pool,Command Group,Command Key都在AbstractCommand這裏實現。
線程池的建立和管理:Hystrix的線程池在HystrixConcurrencyStrategy初始化,線程池是由ThreadPoolExecutor實現的。每一個線程池默認初始化10個線程。Hystrix有個靜態類Factory,建立的線程池會被存儲在Factory中的ConcurrentHashMap中。ConcurrentHashMap的Key則是上文說到的CommandGroupKey或者指定的ThreadPoolKey。每次命令執行的時候,都會根據ThreadPoolKey去找到對應的線程池。線程池擁有一個繼承於rxjava中Scheduler的HystrixContextScheduler,用於在執行命令的時候,把命令在這個線程池上調度執行。
命令的執行:以execute()方法爲例,Hystrix經過toObservable()來構造命令,構造過程當中,定義了整個命令執行過程當中的stage(未開始、執行中、完成執行、執行異常等等)的回調和處理方法。在executeCommandWithSpecifiedIsolation()方法中,使用exjava的subscribeOn方法,傳入上文提到的HystrixContextScheduler對象,經過HystrixContextScheduler的ThreadPoolScheduler把命令submit到ThreadPoolExecutor中去執行。
7.最佳實踐
對於那些原本延遲就比較小的請求(例如訪問本地緩存成功率很高的請求)來講,線程池帶來的開銷是很是高的,這時,你能夠考慮採用其餘方法,例如非阻塞信號量(不支持超時),來實現依賴服務的隔離,使用信號量的開銷很小。但絕大多數狀況下,Netflix 更偏向於使用線程池來隔離依賴服務,由於其帶來的額外開銷能夠接受,而且能支持包括超時在內的全部功能。