1.為什么要進行資源隔離
比如我們現在有3個業務調用分別是查詢訂單、查詢商品、查詢用戶,且這三個業務請求都是依賴第三方服務-訂單服務、商品服務、用戶服務。三個服務均是通過RPC調用。當依賴的訂單服務變慢了,而這個時候后續有大量的查詢訂單請求過來,那么容器中的線程數量則會持續增加直致CPU資源耗盡到100%,整個服務對外不可用,集群環境下就是雪崩。所以,有必要將多個依賴服務的調用分別隔離到各自自己的資源池內,不對其他服務造成影響。如下圖:
2. 兩種隔離方式
Hystrix的隔離策略有兩種:分別是線程隔離和信號量隔離。
THREAD(線程隔離):使用該方式,HystrixCommand將會在單獨的線程上執行,並發請求受線程池中線程數量的限制。
SEMAPHORE(信號量隔離):使用該方式,HystrixCommand將會在調用線程上執行,開銷相對較小,並發請求受信號量的個數的限制。
配置:hystrix.command.default.execution.isolation.strategy 隔離策略,默認是Thread, 可選Thread|Semaphore
2.1 線程隔離(THREAD)
適用場景:適合絕大多數的場景,對依賴服務的網絡調用timeout,TPS要求高的這種問題
執行依賴代碼的線程與請求線程(比如Tomcat線程)分離,請求線程可以自由控制離開的時間,這也是我們通常說的異步編程,Hystrix是結合RxJava來實現的異步編程。通過為每個包裹了HystrixCommand的API接口設置獨立的、固定大小的線程池(hystrix.threadpool.default.coreSize)來控制並發訪問量,當線程飽和的時候可以拒絕服務(走fallback方法),防止依賴問題擴散。
線上建議線程池不要設置過大,否則大量堵塞線程有可能會拖慢服務器。
2.1.1 線程池隔離的優缺點
優點:
一個依賴調用可以給予一個線程池,這個依賴的異常不會影響其他的依賴。
使用線程可以完全隔離業務代碼,請求線程可以快速返回。
可以完全模擬異步調用,方便異步編程。
缺點:
使用線程池的缺點主要是增加了計算的開銷。每一個依賴調用都會涉及到隊列,調度,上下文切換,而這些操作都有可能在不同的線程中執行。
2.1.2 線程池隔離相關參數
讓我們來逐個介紹下@HystrixCommand注解的各個參數:
1:commandKey:配置全局唯一標識服務的名稱,比如,庫存系統有一個獲取庫存服務,那么就可以為這個服務起一個名字來唯一識別該服務,如果不配置,則默認是@HystrixCommand注解修飾的函數的函數名。
2:groupKey:一個比較重要的注解,配置全局唯一標識服務分組的名稱,比如,庫存系統就是一個服務分組。通過設置分組,Hystrix會根據組來組織和統計命令的告、儀表盤等信息。Hystrix命令默認的線程划分也是根據命令組來實現。默認情況下,Hystrix會讓相同組名的命令使用同一個線程池,所以我們需要在創建Hystrix命令時為其指定命令組來實現默認的線程池划分。此外,Hystrix還提供了通過設置threadPoolKey來對線程池進行設置。建議最好設置該參數,使用threadPoolKey來控制線程池組。
例如有如下代碼:
dashboard為
說明:
findById - HystrixCommandKey(默認為Controller下的方法名)
MovieController - HystrixThreadPoolKey(不配置的情況下就是commandGroupKey,HystrixCommandGroupKey默認為類名)
3:threadPoolKey:對線程池進行設定,細粒度的配置,相當於對單個服務的線程池信息進行設置,也可多個服務設置同一個threadPoolKey構成線程組。
4:fallbackMethod:@HystrixCommand注解修飾的函數的回調函數,@HystrixCommand修飾的函數必須和這個回調函數定義在同一個類中,因為定義在了同一個類中,所以fackback method可以是public/private均可。
5:commandProperties:配置該命令的一些參數,如executionIsolationStrategy配置執行隔離策略,默認是使用線程隔離,此處我們配置為THREAD,即線程池隔離。參見:com.netflix.hystrix.HystrixCommandProperties中各個參數的定義。
6:threadPoolProperties:線程池相關參數設置,具體可以設置哪些參數請見:com.netflix.hystrix.HystrixThreadPoolProperties
7:ignoreExceptions:調用服務時,除了HystrixBadRequestException之外,其他@HystrixCommand修飾的函數拋出的異常均會被Hystrix認為命令執行失敗而觸發服務降級的處理邏輯(調用fallbackMethod指定的回調函數),所以當需要在命令執行中拋出不觸發降級的異常時來使用它,通過這個參數指定,哪些異常拋出時不觸發降級(不去調用fallbackMethod),而是將異常向上拋出。
8:observableExecutionMode:定義hystrix observable command的模式;
9:raiseHystrixExceptions:任何不可忽略的異常都包含在HystrixRuntimeException中;
10:defaultFallback:默認的回調函數,該函數的函數體不能有入參,返回值類型與@HystrixCommand修飾的函數體的返回值一致。如果指定了fallbackMethod,則fallbackMethod優先級更高。
2.2 信號量隔離(SEMAPHORE)
用於隔離本地代碼或可快速返回的遠程調用(如memcached,redis)可以直接使用信號量隔離,降低線程隔離的上下文切換開銷。
線程隔離會帶來線程開銷,有些場景(比如無網絡請求場景)可能會因為用開銷換隔離得不償失,為此hystrix提供了信號量隔離。
主要適用場景: 並發需求不大的依賴調用(因為如果並發需求較大,相應的信號量的數量就要設置得夠大,因為Tomcat線程與處理線程為同一個線程,那么這個依賴調用就會占用過多的Tomcat線程資源,有可能會影響到其他服務的接收)
和線程池隔離類似,同一個HystrixCommandGroupKey共用一個信號量(默認為類名)
public class CommandUsingSemaphoreIsolation extends HystrixCommand<String> { private final int id; public CommandUsingSemaphoreIsolation(int id) { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")) // since we're doing an in-memory cache lookup we choose SEMAPHORE isolation .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() .withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE))); this.id = id; } @Override protected String run() { // a real implementation would retrieve data from in memory data structure return "ValueFromHashMap_" + id; } }
2.3 線程池隔離與信號量隔離區別
線程池隔離:
1、調用線程和hystrixCommand線程不是同一個線程,並發請求數受到線程池(不是容器tomcat的線程池,而是hystrixCommand所屬於線程組的線程池)中的線程數限制,默認是10。
2、這個是默認的隔離機制
3、hystrixCommand線程無法獲取到調用線程中的ThreadLocal中的值
信號量隔離:
1、調用線程和hystrixCommand線程是同一個線程,默認最大並發請求數是10
2、調用數度快,開銷小,由於和調用線程是處於同一個線程,所以必須確保調用的微服務可用性足夠高並且返回快才用
注意:如果發生找不到上下文的運行時異常,可考慮將隔離策略設置為SEMAPHONE。
官方圖示: