第二篇文章中提到的熔断机制,是Hystrix解决“雪崩”的方式之一,本文中内容:
- 熔断器的基本原理
- Hystrix对熔断器的实现与使用
1 熔断器的开启
熔断器是和上一篇文章中的命令(每一个命令对应一个熔断器,是熔断的最小单元,我也不知道这样理解对不对...)相对应的,熔断器同样是使用在客户端。
一个命令的熔断器的开启,需要满足下面两个条件(默认情况下):
- 该命令10秒内超过20次请求
- 满足第一个条件的情况下,如果请求的错误百分比大于50%,则打开熔断器
下面通过实验来证明。
创建一个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>
下面创建一个调用服务的命令,在这个命令中设置了超时的时间为500ms,设置了一个是否超时标志isTimeout,使用该标志控制命令的执行是否超时,如下:
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次,这样更容易测试。然后同一个命令调用10次(并且在10秒内)
public static void main(String[] args) throws Exception {
// 10秒内大于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("断路器状态:" + c.isCircuitBreakerOpen() + ",
请求数量:" + hc.getTotalRequests());
//if(c.isCircuitBreakerOpen()) {
//isTimeout = false;
//System.out.println("============ 断路器打开了,等待休眠期结束");
//Thread.sleep(6000);
//}
}
}
下面来看下执行的结果,10秒内请求次数3次满足第一个条件;3次皆为失败,则熔断器打开。
断路器状态:false, 请求数量:0
断路器状态:false, 请求数量:1
断路器状态:false, 请求数量:2
断路器状态:true, 请求数量:3
断路器状态:true, 请求数量:3
断路器状态:true, 请求数量:3
断路器状态:true, 请求数量:3
断路器状态:true, 请求数量:3
断路器状态:true, 请求数量:3
断路器状态:true, 请求数量:3
2 熔断器的关闭
该命令的熔断器打开后,<font color='red'>则该命令默认会有5秒的睡眠时间,在这段时间内,之后的请求直接执行回退方法;5秒之后,会尝试执行一次命令,如果成功则关闭熔断器;否则,熔断器继续打开。</font>
将注释掉的代码是放开,
if(c.isCircuitBreakerOpen()) {
isTimeout = false;
System.out.println("============ 断路器打开了,
等待休眠期结束");
Thread.sleep(6000);
}
查看执行结果:
断路器状态:false, 请求数量:0
断路器状态:false, 请求数量:1
断路器状态:false, 请求数量:2
断路器状态:true, 请求数量:3
============ 断路器打开了,等待休眠期结束
断路器状态:false, 请求数量:0
断路器状态:false, 请求数量:0
断路器状态:false, 请求数量:0
断路器状态:false, 请求数量:3
断路器状态:false, 请求数量:3
断路器状态:false, 请求数量:5
断路器关闭之后,请求数量感觉有点问题,还需要深入理解?
3 线程池隔离
在Hystrix执行的流程中,除了要经过熔断器外,还需要过一关:执行命令的线程池或者信号量是否满载。如果满载,命令就不会执行,直接出发回退逻辑。
线程池针对的最小单元也是命令
创建一个spring boot项目,pom文件内容和上一文相同。首先创建我们调用服务的命令:
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("执行方法,当前索引:"
+ index);
return "";
}
@Override
protected String getFallback() {
+ index);
return "";
}
}
首先将线程次并发数量改为4次,然后通过queue方法异步执行命令。4次执行命令成功,2次执行了回退方法。
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);
}
}
执行回退,当前索引:4
执行回退,当前索引:5
执行方法,当前索引:2
执行方法,当前索引:0
执行方法,当前索引:1
执行方法,当前索引:3
4 信号量隔离
Hystrix默认是线程池隔离。信号量隔离就是每个命令的并发执行数量,当并发数高于阈值时,就不再执行命令。
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);
}
}
执行回退,当前索引:3
执行回退,当前索引:5
执行回退,当前索引:0
执行方法,当前索引:2
执行方法,当前索引:4
执行方法,当前索引:1