一、前言
現在分佈式系統的規模不斷增加,對可用性的要求也越來越高。在各種高可用設計模式中,熔斷、隔離、降級是經常被使用的。而相關的技術,如 Hystrix,便成爲最近的熱點。
從16年初開始,我所在的團隊便開始應用 Hystrix。爲了進一步推動系統可用性升級,普及 Hystrix 在團隊內的使用,我便有了寫一系列 Hystrix 相關的文章的想法。
雖然 Hystrix 有着很詳細的官方文檔,但出於語言和更多細節介紹、實踐建議的考慮,還是需要一些文章作爲補充。
二、什麼是 Hystrix
雖然 Hystrix 已算不上什麼新技術,但還是要簡單介紹一下。
Hystrix 是一個在調用端上,實現斷路器模式,以及隔艙模式,通過避免級聯故障,提高系統容錯能力,從而實現高可用設計的一個 Java 類庫。
上面這段介紹是我的一個簡要總結。更具體的介紹可以參照官網。
三、Hystrix 的基本使用
Hystrix 的主要目的是保護跨進程調用,避免因爲超時等問題,導致的級聯故障。Hystrix 的實現方法是封裝跨進程調用。具體的使用方式有多種:從編程方式看可分爲編程方式和註解方式兩種;從調用方式看可分爲同步調用方式、異步調用方式和反應式調用方式三種。
我們先來看最常見的同步編程方式:
代碼示例
@Component
public class AuthService {
@Autowired
private UserService userService;
public boolean validateUser(String userId) {
User user = new GetUserCommand(userId).execute(); // 6
if (user == null) {
return false;
} else {
return user.isValid();
}
}
class GetUserCommand extends HystrixCommand<User> { // 1
private Long userId;
public GetUserCommand(Long userId) { // 2
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("UserService")) // 3
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
.withCoreSize(20) // 3
)
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(100) // 3
)
);
this.userId = userId;
}
@Override
public User run() throws Exception {
return userService.getUserById(userId); // 4
}
@Override
public User getFallback() {
return new InvalidUser(); // 5
}
}
}
代碼解釋
- Hystrix 常見的使用方法是在一個業務處理類(在本例中是
AuthService
)新建一個內部類(本例中是GetUserCommand
)。這個內部類需要擴展HystrixCommand
。之所以使用內部類是因爲 Hystrix 通常用來封裝一次遠程調用,一般是直接調用一個業務方法。這個業務方法通常位於一個業務處理類或這個業務處理類所依賴的類中。所以使用內部類的方式可以簡化這種調用。擴展HystrixCommand
還需聲明一個泛型類型,這個泛型類型表示這個HystrixCommand
的返回值。 - 定義一個
HystrixCommand
還需定義一個構造函數。這個構造函數十分重要,因爲在使用這個HystrixCommand
時,需要通過構造函數傳遞參數。 - 在構造函數中,需要調用父構造函數對當前的
HystrixCommand
進行配置。主要的配置主要有三個:GroupKey、ThreadPoolSize 和 Timeout。具體的配置方式有多種,較常用的一種方式是通過一個名爲Setter
的 Builder 類進行配置。- GroupKey 通過
Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("UserService"))
語句進行配置; - ThreadPoolSize 通過
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(20))
語句進行配置; - Timeout 通過
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(100)
語句進行配置;
- GroupKey 通過
- 通過實現
run()
方法,在其中實現業務邏輯。通常是調用外部類的方法或外部類依賴的方法。 - 通過實現
getFallback()
方法,實現失敗邏輯,可以在其中實現降級等功能。 - 編寫完
GetUserCommand
之後,使用的時候每次都需要 new 一個新對象,再調用execute()
方法。注意,不要調用run()
方法,否則熔斷、隔離等功能是不生效的。
四、Hystrix 的基本配置
上面的部分介紹了 HystrixCommand
的基本使用方法,但只是簡單介紹了幾個配置。所以,下面將對 HystrixCommand
的相關配置的作用做一個較爲詳細的介紹。
配置模式
Hystrix 的配置有三個維度:全局默認配置、Instance 默認配置、Instance 動態配置。除了少部分配置項外,大部分配置都支持動態修改。接下來介紹一下一些主要參數的 Instance 默認配置方式。這種配置方式也是使用 Hystrix 最先接觸到的配置方式。
Command Group
GroupKey 是 HystrixCommand
不可缺少的配置,其它配置均爲可選。所以,使用 Hystrix 可以使用下面的代碼:
public class CommandHelloWorld extends HystrixCommand<String> {
private final String name;
public CommandHelloWorld(String name) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name;
}
@Override
protected String run() {
return "Hello " + name + "!";
}
}
HystrixCommandGroupKey
是一個接口,所以除了可以使用 HystrixCommandGroupKey.Factory.asKey("ExampleGroup")
的方式定義以外,也可以直接實現這個接口。比如使用如下的方式:
public enum Groups implements HystrixCommandGroupKey {
GROUP_1
}
class EnumGroupCommand extends HystrixCommand<String> {
EnumGroupCommand() {
super(Groups.GROUP_1);
}
@Override
protected String run() throws Exception {
LOGGER.info("Thread of Command: {}", Thread.currentThread().getName());
return null;
}
}
如上面代碼這樣,使用自定義的枚舉類,實現 HystrixCommandGroupKey
接口,可以統一 Hystrix Command Group 的定義,簡化配置。
HystrixCommandGroupKey
的作用主要有兩個:
- 一是起到分組監控、報警的作用。後面的文章會對監控等方面進行介紹;
- 二是在不配置
HystrixThreadPoolKey
的情況下,起到分組線程池的作用。即默認使用HystrixCommandGroupKey
去命名線程池。使用同一個HystrixCommandGroupKey
且沒有自定義HystrixThreadPoolKey
的HystrixCommand
將使用同一個線程池。
Command Thread-Pool
雖然 HystrixCommandGroupKey
可以起到隔離線程池的作用,但是無法起到對線程池進行精細配置的作用。所以這裏就需要線程池進行配置:
Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("MyService"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("MyThreadPool"))
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
.withCoreSize(10)
.withKeepAliveTimeMinutes(1)
.withMaxQueueSize(-1)
)
)
andThreadPoolPropertiesDefaults
配置中的數值表示的是默認值。接下來逐項介紹:
andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("MyThreadPool"))
這是配置 ThreadPoolKey。如果需要在同一個 GroupKey 下面配置不同的 ThreadPool 就需要這個配置。withCoreSize(10)
用來配置線程池大小。Hystrix 對線程池的配置有一些限制,這裏只能配置線程數的 Core Size,不能配置 Max Size。不配置的話使用的默認值是 10。withKeepAliveTimeMinutes(1)
用來配置核心線程數空閒時 keep alive 的時長,默認爲 1 mins。這項配置一般不需要修改。withMaxQueueSize(-1)
用來配置線程池任務隊列的大小,默認值爲 -1。當使用 -1 時,SynchronousQueue
將被使用,即意味着其實這個隊列只是一個交換器,任務將被直接交給工作線程處理。如果工作線程不足,那任務將被拒絕;如果使用任何正整數,LinkedBlockingQueue
將被使用。
Command Properties
這部分是和命令執行直接相關的配置,包括隔離策略、超時時間、Fallback 相關配置。接下來介紹幾個主要的配置:
隔離策略
默認的隔離策略是實現線程池隔離,另外一種隔離策略是 Semaphore。Instance 默認配置可使用如下方法設置:
HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)
這項配置通常不用配置
超時時間
默認時間是1s,單位是毫秒。Instance 默認配置可以使用如下方法設置:
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(100))
這項配置比較重要,後文還會詳細介紹如何調配這個參數。
Fallback 併發度
在 Instance 默認配置中是通過如下代碼設置的:
super(Setter.withGroupKey(BASIC_USAGE_GROUP)
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withFallbackIsolationSemaphoreMaxConcurrentRequests(10)
)
);
默認值爲 10。因爲 getFallback()
方法是和 run()
方法使用同一個線程池執行的,並且不受線程超時時間限制,併發過高會影響主邏輯的執行,所有需要控制併發量。如果 getFallback()
執行速度很快,那不用修改此值。如果 getFallback()
中執行一個較爲耗時的操作,那就需要考慮修改此值(或再通過另一個 HystrixCommand
調用,後續文章會詳細解釋)。
五、小結
本文介紹了 Hystrix 的基本使用方式,看完此文後,讀者應該能通過使用 Hystrix 使得自己的應用具備初步的熔斷降級功能。下一篇文章將介紹 Hystrix 監控相關的內容。
六、參考
文章
- Netflix Hystrix — 應對複雜分佈式系統中的延時和故障容錯:http://www.infoq.com/cn/news/2013/01/netflix-hystrix-fault-tolerance
- Introduction to Hystrix: http://www.baeldung.com/introduction-to-hystrix
作者:編走編想
鏈接:https://www.jianshu.com/p/16440d0ce457
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。