認識Hystrix
Hystrix是Netflix開源的一款容錯框架。在高併發訪問下,系統所依賴的服務的穩定性對系統掉影響非常大。依賴有很多的不可控因素,比如網絡連接慢,資源繁忙,暫時不可用等。只要有其中一個依賴服務不穩定,就會拖跨整個服務。我們要構建穩定、可靠的分佈式系統,就必須要有這樣一套容錯方法。
Hystrix提供了線程隔離、信號量隔離、熔斷、降級回退等容錯方法
容錯流程
隔離
線程隔離
場景:
比如我們現在有3個業務調用分別是查詢訂單、查詢商品、查詢用戶,且這三個業務請求都是依賴第三方服務-訂單服務、商品服務、用戶服務。三個服務均是通過RPC調用。當查詢訂單服務,假如線程阻塞了,這個時候後續有大量的查詢訂單請求過來,那麼容器中的線程數量則會持續增加直致CPU資源耗盡到100%,整個服務對外不可用
此時,每來一個請求(創建一個線程),阻塞在等待查詢上。併發量大,創建了很多線程阻塞在查詢訂單上,耗盡了資源。
線程隔離就是給查詢訂單服務固定大線程數量,與其他服務的線程資源區分開來。這樣就起到了隔離的作用
缺點
線程池的主要缺點就是它增加了計算的開銷,每個業務請求(被包裝成命令)在執行的時候,會涉及到請求排隊,調度和上下文切換
信號量隔離
信號量隔離的方式是限制了總的併發數
,每一次請求過來,請求線程和調用依賴服務的線程是同一個線程.
總結
[圖片上傳失敗...(image-b29ee4-1548676005721)]
線程隔離用在有網絡開銷
信號量隔離用在沒有網絡開銷的地方
使用解析
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)//默認是true,本例中爲了展現該參數
.withCircuitBreakerForceOpen(false)//默認是false,本例中爲了展現該參數
.withCircuitBreakerForceClosed(false)//默認是false,本例中爲了展現該參數
.withCircuitBreakerErrorThresholdPercentage(5)//(1)錯誤百分比超過5%
.withCircuitBreakerRequestVolumeThreshold(10)//(2)10s以內調用次數10次,同時滿足(1)(2)熔斷器打開
.withCircuitBreakerSleepWindowInMilliseconds(5000)//隔5s之後,熔斷器會嘗試半開(關閉),重新放進來請求
.withExecutionTimeoutInMilliseconds(1000)
)
.andThreadPoolPropertiesDefaults(
HystrixThreadPoolProperties.Setter()
.withMaxQueueSize(10) //配置隊列大小
.withCoreSize(2) // 配置線程池裏的線程數
)
);
}
@Override
protected String run() throws Exception {
Random rand = new Random();
//模擬錯誤百分比(方式比較粗魯但可以證明問題)
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{
public static void main(String[] args) throws InterruptedException {
for(int i=0;i<35;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以後,熔斷器嘗試關閉,放開新的請求進來
}
}
}
}
初始化
HystrixCommandKey
Hystrix使用單例模式存儲HystrixCommand,熔斷機制就是根據單實例上的調用情況統計實現的
,所以每個HystrixCommand要有自己的名字,用於區分,同時用於依賴調用的隔離。HystrixCommandKey就是用於定義這個名字,如果沒有定義這個名字,Hystrix會使用其類名作爲其名字,可以使用HystrixCommandKey.Factory.asKey(String name)方法定義一個名稱。
HystrixThreadPoolKey
HystrixThreadPoolKey是HystrixCommand所在的線程池,如果該參數不設置則使用HystrixCommandGroupKey作爲HystrixThreadPoolKey,這種情況下同一個HystrixCommandGroupKey下的依賴調用共用同一個線程池內
,如果不想共用同一個線程池,則需要設置該參數。可以使用HystrixThreadPoolKey.Factory.asKey(String name)方法設置。
HystrixCommandGroupKey
Hystrix需要對HystrixCommand進行分組,便於統計、管理,所以需要一個分組名稱,HystrixCommandGroupKey就是用於定義分組名稱
,可以使用HystrixCommandGroupKey.Factory.asKey(String name)方法定義一個分組名。每個HystrixCommand必須要配置一個分組名,一個是用於分組,還有如果沒有配置HystrixThreadPoolKey,這個分組名將會用於線程池名。
HystrixCommandProperties
這個就是HystrixCommand的屬性配置,它可以設置熔斷器是否可用、熔斷器熔斷的錯誤百分比、依賴調用超時時間等
,它有一些默認的配置參數,如熔斷器熔斷的錯誤百分比默認值是50%、依賴調用超時時間默認值是1000毫秒。
HystrixThreadPoolProperties
從名稱上可以看出這是線程池的屬性配置
,可以通過它設置核心線程數大小、最大線程數、任務隊列大小等,當然它也又一些默認的配置參數。
命令執行
execute():以同步堵塞方式執行run()。調用execute()後,hystrix先創建一個新線程運行run(),接着調用程序要在execute()調用處一直堵塞着,直到run()運行完成。
queue():以異步非堵塞方式執行run()。調用queue()就直接返回一個Future對象,同時hystrix創建一個新線程運行run(),調用程序通過Future.get()拿到run()的返回結果,而Future.get()是堵塞執行的。
observe():不研究
toObservable():不研究
降級
使用fallback機制很簡單,繼承HystrixCommand只需重寫getFallback()
Dubbo 的服務降級策略與 Hystrix 的熔斷機制的簡單對比
一開始我有個疑問:Dubbo提供了服務降級策略,爲什麼說Hystrix對RPC(對網絡開消)的服務容錯呢?
Dubbo 提供的降級策略比較簡單,Dubbo 的降級可以通過代碼設置或者通過管理控制檯進行設置,並且一旦設置爲了降級,那麼所有消費者調用降級接口時候都是用本地 mock 值,並沒有自動恢復策略,並且必須讓管理員手動進行降級或者取消降級
Hystrix 除了提供 Dubbo 類似的降級策略外,還提供了自適應的降級恢復策略,當調用服務異常次數超過指定閾值後會自動進行降級,並且降級後不是一直都是用降級方案,它可以在降級後時間窗口過後再次進行嘗試遠程調用,如果目前服務已經 OK,那麼會自動關閉降級,這種自反饋的降級,降級恢復策略在整個過程中都自動完成的,不需要人工干涉。