本文是本人学习Hystrix
总结的第四篇,这次通过一个例子程序学习使用Hystrix
模拟远程调用,并调整参数观察调用失败对熔断器的影响,这次的例子中没有使用fallback
机制,也就是说,调用失败的时候会抛出异常而不是执行fallback
逻辑。本应用特征如下 :
- 纯
java
项目 ;- 暂不涉及结合
Spring
使用,以便专注于Hystrix
自身;
- 暂不涉及结合
- 使用
junit
观察运行效果 ; - 使用
maven
管理依赖;
这里所模拟的远程调用通过
sleep
阻塞线程一段时间模拟一个耗时的远程调用,并无真正的远程调用发生。
1. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>tut.zero</groupId>
<artifactId>hystrix</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>1.5.18</version>
</dependency>
<!-- test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
2. 模拟一个远程服务调用
package tut.zero.middle;
/**
* 模拟一个远程服务调用
*/
public class MockRemoteService {
private long wait;
/**
* @param wait 模拟远程调用所消耗的时间,单位 : 毫秒
* @throws InterruptedException
*/
public MockRemoteService(long wait) {
this.wait = wait;
}
String execute() throws InterruptedException {
Thread.sleep(wait);
return "SUCCESS";
}
}
这是一个纯粹通过Java
模拟远程调用的类,通过Thread.sleep()
来模拟远程调用的执行耗时,而并不真正发生远程调用。该类和Hystrix
还没有任何关系。
2. 定义封装远程调用的Hystrix Command
package tut.zero.middle;
import com.netflix.hystrix.HystrixCommand;
/**
* 封装一个模拟进行远程调用的 HystrixCommand, 返回结果设计为 String 类型,
* 没有fallback实现
*/
public class MockRemoteServiceCommandWithFallback extends HystrixCommand<String> {
private MockRemoteService remoteService;
public MockRemoteServiceCommandWithFallback(Setter config, MockRemoteService remoteService) {
super(config);
this.remoteService = remoteService;
}
@Override
protected String run() throws Exception {
return remoteService.execute();
}
}
3. 测试运行定义的Hystrix Command
package tut.zero;
import tut.zero.middle.MockRemoteService;
import tut.zero.middle.MockRemoteServiceCommand;
import tut.zero.simple.CommandHelloWorld;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.HystrixThreadPoolProperties;
import com.netflix.hystrix.exception.HystrixRuntimeException;
import org.junit.Test;
import java.util.concurrent.Future;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
public class HystrixTest {
@Test
public void testMockRemoteServiceCallWithCircuitBreakerButNoFallback() {
HystrixCommand.Setter config = HystrixCommand.Setter
.withGroupKey(
HystrixCommandGroupKey.Factory.asKey("MockRemoteServiceCircuitBreaker"));
HystrixCommandProperties.Setter properties = HystrixCommandProperties.Setter();
properties.withExecutionTimeoutInMilliseconds(1_000); // 每次任务执行超过1秒就认为是超时失败
final int circuitBreakerSleepWindowInMills = 4_000; // 设置熔断之后多长时间恢复调用:4秒
properties.withCircuitBreakerSleepWindowInMilliseconds(circuitBreakerSleepWindowInMills);
// 1. 属性 errorThresholdPercentage 表示错误百分比,默认值50%,例如一段时间(10s)内有100个请求,
// 其中有55个超时或者异常返回了,那么这段时间内的错误百分比是55%,大于了默认值50%,这种情况下触发
// 熔断器打开。
// 2. 属性 circuitBreakerRequestVolumeThreshold 默认为 20, 意思是至少有20个请求才进行
// errorThresholdPercentage 错误百分比计算。
// 这里设置为 1, 表示累计调用1时次开始进行 errorThresholdPercentage 错误百分比计算。
properties.withCircuitBreakerRequestVolumeThreshold(1);
config.andCommandPropertiesDefaults(properties);
// 第一次触发远程调用,但会触发超时失败
assertThat(invokeRemoteService(config, 1, 2_000), containsString("timed-out"));
// 第二次触发远程调用,但会触发超时失败
assertThat(invokeRemoteService(config, 2, 2_000), containsString("timed-out"));
// 第三次不再触发远程调用,因为熔断器已经打开,且没有fallback实现,所以抛出HystrixRuntimeException
assertThat(invokeRemoteService(config, 3, 2_000), containsString("short-circuited"));
// 第四次不再触发远程调用,因为熔断器已经打开,且没有fallback实现,所以抛出HystrixRuntimeException
assertThat(invokeRemoteService(config, 4, 2_000), containsString("short-circuited"));
// 以上4次远程调用每次都需要2秒,但实际上因为超时容忍为1秒,所以前两次远程调用都超时失败了。
// 第二次之后失败次数2开始变成大于1(基于 错误百分比 errorThresholdPercentage 的熔断器开关打开),
// 所以导致第3,4次远程调用根本不会发生。
// 因为 breaker sleep window 是 4 秒, 所以此时 4 秒后 断路器才会放行新的请求。
// 下面休眠了 4秒 多一点点(10毫秒)是为了确保尽量不受线程调度的抖动影响
try {
Thread.sleep(circuitBreakerSleepWindowInMilliseconds + 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 现在经过休眠,熔断器已经恢复关闭状态, 允许远程调用继续进行。
// 以下模拟的调用都是在足够短的时间内完成,所以都会成功返回。
assertThat(invokeRemoteService(config, 5, 500), equalTo("SUCCESS"));
assertThat(invokeRemoteService(config, 6, 800), equalTo("SUCCESS"));
assertThat(invokeRemoteService(config, 7, 950), equalTo("SUCCESS"));
}
}
/**
* 工具方法,调用远程服务,成功时返回结果,失败时返回异常消息
*
* @param config
* @param count
* @param timeout
* @return
*/
public static String invokeRemoteService(HystrixCommand.Setter config, int count, int timeout) {
try {
MockRemoteServiceCommand command = new MockRemoteServiceCommand(config,
new MockRemoteService(timeout));
final boolean isCircuitBreakerOpen = command.isCircuitBreakerOpen();
final String result = command.execute();
System.out.println("第" + count + "次调用成功 :" + result);
return result;
} catch (HystrixRuntimeException ex) {
System.out.println("第" + count + "次调用失败 : ex = " + ex);
return ex.getMessage();
} catch (Exception e) {
return e.getMessage();
}
}
该测试方法执行时控制台输出为 :
第1次调用失败 : ex = com.netflix.hystrix.exception.HystrixRuntimeException: MockRemoteServiceCommand timed-out and no fallback available.
第2次调用失败 : ex = com.netflix.hystrix.exception.HystrixRuntimeException: MockRemoteServiceCommand timed-out and no fallback available.
第3次调用失败 : ex = com.netflix.hystrix.exception.HystrixRuntimeException: MockRemoteServiceCommand short-circuited and no fallback available.
第4次调用失败 : ex = com.netflix.hystrix.exception.HystrixRuntimeException: MockRemoteServiceCommand short-circuited and no fallback available.
第5次调用成功 :SUCCESS
第6次调用成功 :SUCCESS
第7次调用成功 :SUCCESS