Hystrix使用入門手冊(中文)

原文鏈接:https://www.jianshu.com/p/b9af028efebb

https://www.jianshu.com/p/b9af028efebb

導語:網上資料(尤其中文文檔)對hystrix基礎功能的解釋比較籠統,看了往往一頭霧水。爲此,本文將通過若干demo,加入對官網How-it-Works的理解和翻譯,力求更清晰解釋hystrix的基礎功能。所用demo均對官網How-To-Use進行了二次修改,見https://github.com/star2478/java-hystrix

Hystrix是Netflix開源的一款容錯系統,能幫助使用者碼出具備強大的容錯能力和魯棒性的程序。如果某程序或class要使用Hystrix,只需簡單繼承HystrixCommand/HystrixObservableCommand並重寫run()/construct(),然後調用程序實例化此class並執行execute()/queue()/observe()/toObservable()

// HelloWorldHystrixCommand要使用Hystrix功能 
public class HelloWorldHystrixCommand extends HystrixCommand {  
    private final String name; 
    public HelloWorldHystrixCommand(String name) {   
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));     
        this.name = name; 
    } 
    // 如果繼承的是HystrixObservableCommand,要重寫Observable construct() 
    @Override 
    protected String run() {     
        return "Hello " + name; 
    } 
} 
/* 調用程序對HelloWorldHystrixCommand實例化,執行execute()即觸發HelloWorldHystrixCommand.run()的執行 */ 
String result = new HelloWorldHystrixCommand("HLX").execute();
System.out.println(result);  // 打印出Hello HLX 

pom.xml加上以下依賴。spring cloud也集成了hystrix,不過本文只介紹原生hystrix。

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

本文重點介紹的是Hystrix各項基礎能力的用法及其效果,不從零介紹hystrix,要了解基礎知識推薦官網wiki民間blog

1、HystrixCommand vs HystrixObservableCommand

要想使用hystrix,只需要繼承HystrixCommandHystrixObservableCommand,簡單用法見上面例子。兩者主要區別是:

  • 前者的命令邏輯寫在run();後者的命令邏輯寫在construct()

  • 前者的run()是由新創建的線程執行;後者的construct()是由調用程序線程執行

  • 前者一個實例只能向調用程序發送(emit)單條數據,比如上面例子中run()只能返回一個String結果;後者一個實例可以順序發送多條數據,比如demo中順序調用多個onNext(),便實現了向調用程序發送多條數據,甚至還能發送一個範圍的數據集

2、4個命令執行方法

execute()queue()observe()toObservable()這4個方法用來觸發執行run()/construct(),一個實例只能執行一次這4個方法,特別說明的是HystrixObservableCommand沒有execute()queue()

4個方法的主要區別是:

  • execute():以同步堵塞方式執行run()。以demo爲例,調用execute()後,hystrix先創建一個新線程運行run(),接着調用程序要在execute()調用處一直堵塞着,直到run()運行完成

  • queue():以異步非堵塞方式執行run()。以demo爲例,一調用queue()就直接返回一個Future對象,同時hystrix創建一個新線程運行run(),調用程序通過Future.get()拿到run()的返回結果,而Future.get()是堵塞執行的

  • observe():事件註冊前執行run()/construct()。以demo爲例,第一步是事件註冊前,先調用observe()自動觸發執行run()/construct()(如果繼承的是HystrixCommand,hystrix將創建新線程非堵塞執行run();如果繼承的是HystrixObservableCommand,將以調用程序線程堵塞執行construct()),第二步是從observe()返回後調用程序調用subscribe()完成事件註冊,如果run()/construct()執行成功則觸發onNext()onCompleted(),如果執行異常則觸發onError()

  • toObservable():事件註冊後執行run()/construct()。以demo爲例,第一步是事件註冊前,一調用toObservable()就直接返回一個Observable<String>對象,第二步調用subscribe()完成事件註冊後自動觸發執行run()/construct()(如果繼承的是HystrixCommand,hystrix將創建新線程非堵塞執行run(),調用程序不必等待run();如果繼承的是HystrixObservableCommand,將以調用程序線程堵塞執行construct(),調用程序等待construct()執行完才能繼續往下走),如果run()/construct()執行成功則觸發onNext()onCompleted(),如果執行異常則觸發onError()

3、fallback(降級)

使用fallback機制很簡單,繼承HystrixCommand只需重寫getFallback(),繼承HystrixObservableCommand只需重寫resumeWithFallback(),比如HelloWorldHystrixCommand加上下面代碼片段:

@Override
protected String getFallback() {
    return "fallback: " + name;
}

fallback實際流程是當run()/construct()被觸發執行時或執行中發生錯誤時,將轉向執行getFallback()/resumeWithFallback()。結合下圖,4種錯誤情況將觸發fallback:

  • 非HystrixBadRequestException異常:以demo爲例,當拋出HystrixBadRequestException時,調用程序可以捕獲異常,沒有觸發getFallback(),而其他異常則會觸發getFallback(),調用程序將獲得getFallback()的返回

  • run()/construct()運行超時:以demo爲例,使用無限while循環或sleep模擬超時,觸發了getFallback()

  • 熔斷器啓動:以demo爲例,我們配置10s內請求數大於3個時就啓動熔斷器,請求錯誤率大於80%時就熔斷,然後for循環發起請求,當請求符合熔斷條件時將觸發getFallback()。更多熔斷策略見下文

  • 線程池/信號量已滿:以demo爲例,我們配置線程池數目爲3,然後先用一個for循環執行queue(),觸發的run()sleep 2s,然後再用第2個for循環執行execute(),發現所有execute()都觸發了fallback,這是因爲第1個for的線程還在sleep,佔用着線程池所有線程,導致第2個for的所有命令都無法獲取到線程

來自hystrix github wiki

調用程序可以通過isResponseFromFallback()查詢結果是由run()/construct()還是getFallback()/resumeWithFallback()返回的

4、隔離策略

hystrix提供了兩種隔離策略:線程池隔離和信號量隔離。hystrix默認採用線程池隔離。

  • 線程池隔離:不同服務通過使用不同線程池,彼此間將不受影響,達到隔離效果。以demo爲例,我們通過andThreadPoolKey配置使用命名爲ThreadPoolTest的線程池,實現與其他命名的線程池天然隔離,如果不配置andThreadPoolKey則使用withGroupKey配置來命名線程池

  • 信號量隔離:線程隔離會帶來線程開銷,有些場景(比如無網絡請求場景)可能會因爲用開銷換隔離得不償失,爲此hystrix提供了信號量隔離,當服務的併發數大於信號量閾值時將進入fallback。以demo爲例,通過withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)配置爲信號量隔離,通過withExecutionIsolationSemaphoreMaxConcurrentRequests配置執行併發數不能大於3,由於信號量隔離下無論調用哪種命令執行方法,hystrix都不會創建新線程執行run()/construct(),所以調用程序需要自己創建多個線程來模擬併發調用execute(),最後看到一旦併發線程>3,後續請求都進入fallback

5、熔斷機制

熔斷機制相當於電路的跳閘功能,舉個栗子,我們可以配置熔斷策略爲當請求錯誤比例在10s內>50%時,該服務將進入熔斷狀態,後續請求都會進入fallback。

demo爲例,我們通過withCircuitBreakerRequestVolumeThreshold配置10s內請求數超過3個時熔斷器開始生效,通過withCircuitBreakerErrorThresholdPercentage配置錯誤比例>80%時開始熔斷,然後for循環執行execute()觸發run(),在run()裏,如果name是小於10的偶數則正常返回,否則超時,通過多次循環後,超時請求佔所有請求的比例將大於80%,就會看到後續請求都不進入run()而是進入getFallback(),因爲不再打印"running run():" + name了。

除此之外,hystrix還支持多長時間從熔斷狀態自動恢復等功能,見下文附錄。

6、結果cache

hystrix支持將一個請求結果緩存起來,下一個具有相同key的請求將直接從緩存中取出結果,減少請求開銷。要使用hystrix cache功能,第一個要求是重寫getCacheKey(),用來構造cache key;第二個要求是構建context,如果請求B要用到請求A的結果緩存,A和B必須同處一個context。通過HystrixRequestContext.initializeContext()context.shutdown()可以構建一個context,這兩條語句間的所有請求都處於同一個context。

demotestWithCacheHits()爲例,command2acommand2bcommand2c同處一個context,前兩者的cache key都是2HLX(見getCacheKey()),所以command2a執行完後把結果緩存,command2b執行時就不走run()而是直接從緩存中取結果了,而command2c的cache key是2HLX1,無法從緩存中取結果。此外,通過isResponseFromCache()可檢查返回結果是否來自緩存。

7、合併請求collapsing

hystrix支持N個請求自動合併爲一個請求,這個功能在有網絡交互的場景下尤其有用,比如每個請求都要網絡訪問遠程資源,如果把請求合併爲一個,將使多次網絡交互變成一次,極大節省開銷。重要一點,兩個請求能自動合併的前提是兩者足夠“近”,即兩者啓動執行的間隔時長要足夠小,默認爲10ms,即超過10ms將不自動合併。

demo爲例,我們連續發起多個queue請求,依次返回f1~f6共6個Future對象,根據打印結果可知f1~f5同處一個線程,說明這5個請求被合併了,而f6由另一個線程執行,這是因爲f5f6中間隔了一個sleep,超過了合併要求的最大間隔時長。

附錄:各種策略配置

根據http://hot66hot.iteye.com/blog/2155036 整理而得。

  • HystrixCommandProperties
/* --------------統計相關------------------*/ 
// 統計滾動的時間窗口,默認:5000毫秒(取自circuitBreakerSleepWindowInMilliseconds)   
private final HystrixProperty metricsRollingStatisticalWindowInMilliseconds;   
// 統計窗口的Buckets的數量,默認:10個,每秒一個Buckets統計   
private final HystrixProperty metricsRollingStatisticalWindowBuckets; // number of buckets in the statisticalWindow   
// 是否開啓監控統計功能,默認:true   
private final HystrixProperty metricsRollingPercentileEnabled;   
/* --------------熔斷器相關------------------*/ 
// 熔斷器在整個統計時間內是否開啓的閥值,默認20。也就是在metricsRollingStatisticalWindowInMilliseconds(默認10s)內至少請求20次,熔斷器才發揮起作用   
private final HystrixProperty circuitBreakerRequestVolumeThreshold;   
// 熔斷時間窗口,默認:5秒.熔斷器中斷請求5秒後會進入半打開狀態,放下一個請求進來重試,如果該請求成功就關閉熔斷器,否則繼續等待一個熔斷時間窗口
private final HystrixProperty circuitBreakerSleepWindowInMilliseconds;   
//是否啓用熔斷器,默認true. 啓動   
private final HystrixProperty circuitBreakerEnabled;   
//默認:50%。當出錯率超過50%後熔斷器啓動
private final HystrixProperty circuitBreakerErrorThresholdPercentage;  
//是否強制開啓熔斷器阻斷所有請求,默認:false,不開啓。置爲true時,所有請求都將被拒絕,直接到fallback 
private final HystrixProperty circuitBreakerForceOpen;   
//是否允許熔斷器忽略錯誤,默認false, 不開啓   
private final HystrixProperty circuitBreakerForceClosed; 
/* --------------信號量相關------------------*/ 
//使用信號量隔離時,命令調用最大的併發數,默認:10   
private final HystrixProperty executionIsolationSemaphoreMaxConcurrentRequests;   
//使用信號量隔離時,命令fallback(降級)調用最大的併發數,默認:10   
private final HystrixProperty fallbackIsolationSemaphoreMaxConcurrentRequests; 
/* --------------其他------------------*/ 
//使用命令調用隔離方式,默認:採用線程隔離,ExecutionIsolationStrategy.THREAD   
private final HystrixProperty executionIsolationStrategy;   
//使用線程隔離時,調用超時時間,默認:1秒   
private final HystrixProperty executionIsolationThreadTimeoutInMilliseconds;   
//線程池的key,用於決定命令在哪個線程池執行   
private final HystrixProperty executionIsolationThreadPoolKeyOverride;   
//是否開啓fallback降級策略 默認:true   
private final HystrixProperty fallbackEnabled;   
// 使用線程隔離時,是否對命令執行超時的線程調用中斷(Thread.interrupt())操作.默認:true   
private final HystrixProperty executionIsolationThreadInterruptOnTimeout; 
// 是否開啓請求日誌,默認:true   
private final HystrixProperty requestLogEnabled;   
//是否開啓請求緩存,默認:true   
private final HystrixProperty requestCacheEnabled; // Whether request caching is enabled. 
  • HystrixCollapserProperties
//請求合併是允許的最大請求數,默認: Integer.MAX_VALUE   
private final HystrixProperty maxRequestsInBatch;   
//批處理過程中每個命令延遲的時間,默認:10毫秒   
private final HystrixProperty timerDelayInMilliseconds;   
//批處理過程中是否開啓請求緩存,默認:開啓   
private final HystrixProperty requestCacheEnabled; 
  • HystrixThreadPoolProperties
/* 配置線程池大小,默認值10個 */ 
private final HystrixProperty corePoolSize; 
/* 配置線程值等待隊列長度,默認值:-1 建議值:-1表示不等待直接拒絕,測試表明線程池使用直接決絕策略+ 合適大小的非回縮線程池效率最高.所以不建議修改此值。 當使用非回縮線程池時,queueSizeRejectionThreshold,keepAliveTimeMinutes 參數無效 */
private final HystrixProperty maxQueueSize; 

參考文獻

https://github.com/Netflix/Hystrix
https://github.com/Netflix/Hystrix/wiki/How-To-Use
http://hot66hot.iteye.com/blog/2155036



作者:star24
鏈接:https://www.jianshu.com/p/b9af028efebb
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章