第二篇文章中提到的熔斷機制,是Hystrix解決“雪崩”的方式之一,本文中內(nèi)容:
- 熔斷器的基本原理
- Hystrix對熔斷器的實現(xiàn)與使用
1 熔斷器的開啟
熔斷器是和上一篇文章中的命令(每一個命令對應一個熔斷器,是熔斷的最小單元,我也不知道這樣理解對不對...)相對應的,熔斷器同樣是使用在客戶端。
一個命令的熔斷器的開啟,需要滿足下面兩個條件(默認情況下):
- 該命令10秒內(nèi)超過20次請求
- 滿足第一個條件的情況下,如果請求的錯誤百分比大于50%,則打開熔斷器
下面通過實驗來證明。
創(chuàng)建一個spring boot項目,pom依賴如下:
<dependencies>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>1.5.12</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<version>1.7.25</version>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
</dependencies>
下面創(chuàng)建一個調(diào)用服務的命令,在這個命令中設置了超時的時間為500ms,設置了一個是否超時標志isTimeout,使用該標志控制命令的執(zhí)行是否超時,如下:
static class TestCommand extends HystrixCommand<String> {
private boolean isTimeout;
public TestCommand(boolean isTimeout) {
super(Setter.withGroupKey(
HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(500)));
this.isTimeout = isTimeout;
}
@Override
protected String run() throws Exception {
if(isTimeout) {
Thread.sleep(800);
} else {
Thread.sleep(200);
}
return "";
}
@Override
protected String getFallback() {
return "fallback";
}
}
可以通過 netflix.config.ConfigurationManager 配置類來將第一個條件的20次改為3次,這樣更容易測試。然后同一個命令調(diào)用10次(并且在10秒內(nèi))
public static void main(String[] args) throws Exception {
// 10秒內(nèi)大于3次請求,滿足第一個條件
ConfigurationManager
.getConfigInstance()
.setProperty(
"hystrix.command.default.circuitBreaker.requestVolumeThreshold",
3);
boolean isTimeout = true;
for(int i = 0; i < 10; i++) {
TestCommand c = new TestCommand(isTimeout);
c.execute();
HealthCounts hc = c.getMetrics().getHealthCounts();
System.out.println("斷路器狀態(tài):" + c.isCircuitBreakerOpen() + ",
請求數(shù)量:" + hc.getTotalRequests());
//if(c.isCircuitBreakerOpen()) {
//isTimeout = false;
//System.out.println("============ 斷路器打開了,等待休眠期結束");
//Thread.sleep(6000);
//}
}
}
下面來看下執(zhí)行的結果,10秒內(nèi)請求次數(shù)3次滿足第一個條件;3次皆為失敗,則熔斷器打開。
斷路器狀態(tài):false, 請求數(shù)量:0
斷路器狀態(tài):false, 請求數(shù)量:1
斷路器狀態(tài):false, 請求數(shù)量:2
斷路器狀態(tài):true, 請求數(shù)量:3
斷路器狀態(tài):true, 請求數(shù)量:3
斷路器狀態(tài):true, 請求數(shù)量:3
斷路器狀態(tài):true, 請求數(shù)量:3
斷路器狀態(tài):true, 請求數(shù)量:3
斷路器狀態(tài):true, 請求數(shù)量:3
斷路器狀態(tài):true, 請求數(shù)量:3
2 熔斷器的關閉
該命令的熔斷器打開后,<font color='red'>則該命令默認會有5秒的睡眠時間,在這段時間內(nèi),之后的請求直接執(zhí)行回退方法;5秒之后,會嘗試執(zhí)行一次命令,如果成功則關閉熔斷器;否則,熔斷器繼續(xù)打開。</font>
將注釋掉的代碼是放開,
if(c.isCircuitBreakerOpen()) {
isTimeout = false;
System.out.println("============ 斷路器打開了,
等待休眠期結束");
Thread.sleep(6000);
}
查看執(zhí)行結果:
斷路器狀態(tài):false, 請求數(shù)量:0
斷路器狀態(tài):false, 請求數(shù)量:1
斷路器狀態(tài):false, 請求數(shù)量:2
斷路器狀態(tài):true, 請求數(shù)量:3
============ 斷路器打開了,等待休眠期結束
斷路器狀態(tài):false, 請求數(shù)量:0
斷路器狀態(tài):false, 請求數(shù)量:0
斷路器狀態(tài):false, 請求數(shù)量:0
斷路器狀態(tài):false, 請求數(shù)量:3
斷路器狀態(tài):false, 請求數(shù)量:3
斷路器狀態(tài):false, 請求數(shù)量:5
斷路器關閉之后,請求數(shù)量感覺有點問題,還需要深入理解?
3 線程池隔離
在Hystrix執(zhí)行的流程中,除了要經(jīng)過熔斷器外,還需要過一關:執(zhí)行命令的線程池或者信號量是否滿載。如果滿載,命令就不會執(zhí)行,直接出發(fā)回退邏輯。
線程池針對的最小單元也是命令
創(chuàng)建一個spring boot項目,pom文件內(nèi)容和上一文相同。首先創(chuàng)建我們調(diào)用服務的命令:
public class MyCommand extends HystrixCommand<String> {
private int index;
public MyCommand(int index) {
super(Setter.withGroupKey(
HystrixCommandGroupKey.Factory
.asKey("TestGroupKey")));
this.index = index;
}
@Override
protected String run() throws Exception {
Thread.sleep(500);
System.out.println("執(zhí)行方法,當前索引:"
+ index);
return "";
}
@Override
protected String getFallback() {
+ index);
return "";
}
}
首先將線程次并發(fā)數(shù)量改為4次,然后通過queue方法異步執(zhí)行命令。4次執(zhí)行命令成功,2次執(zhí)行了回退方法。
public class ThreadMain {
public static void main(String[] args) throws Exception {
ConfigurationManager.getConfigInstance().
setProperty(default.coreSize, 4);
for(int i = 0; i < 6; i++) {
MyCommand c = new MyCommand(i);
c.queue();
}
Thread.sleep(5000);
}
}
執(zhí)行回退,當前索引:4
執(zhí)行回退,當前索引:5
執(zhí)行方法,當前索引:2
執(zhí)行方法,當前索引:0
執(zhí)行方法,當前索引:1
執(zhí)行方法,當前索引:3
4 信號量隔離
Hystrix默認是線程池隔離。信號量隔離就是每個命令的并發(fā)執(zhí)行數(shù)量,當并發(fā)數(shù)高于閾值時,就不再執(zhí)行命令。
public class SemaphoreMain {
public static void main(String[] args) throws Exception {
ConfigurationManager
.getConfigInstance().setProperty(
"hystrix.command.default.
execution.isolation.strategy", ExecutionIsolationStrategy.SEMAPHORE);
.getConfigInstance().setProperty(
"hystrix.command.default
.execution.isolation.semaphore.maxConcurrentRequests", 3);
for(int i = 0; i < 6; i++) {
final int index = i;
Thread t = new Thread() {
public void run() {
MyCommand c = new MyCommand(index);
c.execute();
}
};
t.start();
}
Thread.sleep(5000);
}
}
執(zhí)行回退,當前索引:3
執(zhí)行回退,當前索引:5
執(zhí)行回退,當前索引:0
執(zhí)行方法,當前索引:2
執(zhí)行方法,當前索引:4
執(zhí)行方法,當前索引:1