Hystrix 概述
典型的分布式系统由许多协同工作的服务组成。
这些服务容易出现故障或延迟响应。如果一个服务失败,它可能会影响其他服务,影响性能,并可能使应用程序的其他部分无法访问,或者在最坏的情况下使整个应用程序瘫痪。
当然,有一些解决方案可以帮助应用程序具有弹性和容错性——Hystrix就是这样一个框架。
Hystrix框架库通过提供容错性和延迟性来帮助控制服务之间的交互。它通过隔离故障服务和阻止故障的级联效应来提高系统的整体弹性。
在这一系列的文章中,我们将首先看看当服务或系统出现故障时,Hystrix是如何救援的,以及在这种情况下Hystrix可以完成什么。
简单示例
- 引入依赖
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>1.5.4</version>
</dependency>
<dependency>
<groupId>com.netflix.rxjava</groupId>
<artifactId>rxjava-core</artifactId>
<version>0.20.7</version>
</dependency>
- 简单示例
public class CommandHelloWorld extends HystrixCommand<String> {
private String name;
public CommandHelloWorld(Setter config, String name) {
super(config);
this.name = name;
}
@Override
protected String run() throws Exception {
return "Hello " + name + "!";
}
}
测试
@Test
void givenInputBobAndDefaultSettings_whenCommandExecuted_thenReturnHelloBob() {
System.out.println(new CommandHelloWorld("Bob").execute());
}
结果打印结果 : Hello Bob
模拟远程服务示例
让我们从模拟一个真实世界的例子开始。在下面的示例中,
类RemoteServiceTestSimulator表示远程服务器上的一个服务。
它有一个方法,在给定的一段时间后给出消息响应。我们可以想象,这种等待是对远程系统上耗时进程的模拟,导致对调用服务的响应延迟:
class RemoteServiceTestSimulator {
private long wait;
RemoteServiceTestSimulator(long wait) throws InterruptedException {
this.wait = wait;
}
String execute() throws InterruptedException {
Thread.sleep(wait);
return "Success";
}
}
class RemoteServiceTestCommand extends HystrixCommand<String> {
private RemoteServiceTestSimulator remoteService;
RemoteServiceTestCommand(Setter config, RemoteServiceTestSimulator remoteService) {
super(config);
this.remoteService = remoteService;
}
@Override
protected String run() throws Exception {
return remoteService.execute();
}
}
调用通过调用RemoteServiceTestCommand对象实例上的execute()方法来执行。
@Test
public void givenSvcTimeoutOf100AndDefaultSettings_whenRemoteSvcExecuted_thenReturnSuccess()
throws InterruptedException {
HystrixCommand.Setter config = HystrixCommand
.Setter
// 1. 设置组的名称
.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceGroup2"));
// 2. 调用远程服务
assertThat(new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(100)).execute(),
equalTo("Success"));
}
到目前为止,我们已经看到了如何在HystrixCommand对象中包装远程服务调用。
在下一节中,让我们看看如何处理远程服务开始恶化的情况。
模擬远程服务防御示例
带有超时的防御性编程为远程服务的调用设置超时是一般的编程实践。
让我们首先看看如何在HystrixCommand上设置超时,以及它如何通过短路来解决问题的:
@Test
public void givenSvcTimeoutOf5000AndExecTimeoutOf10000_whenRemoteSvcExecuted_thenReturnSuccess()
throws InterruptedException {
HystrixCommand.Setter config = HystrixCommand
.Setter
// 1. 设置组的名称
.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceGroupTest4"));
HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter();
// 2. 设置超时时间
commandProperties.withExecutionTimeoutInMilliseconds(10_000);
config.andCommandPropertiesDefaults(commandProperties);
// 3. 调用远程服务
assertThat(new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(500)).execute(),
equalTo("Success"));
}
在上面的测试中,我们通过将超时设置为500毫秒来延迟服务的响应。
我们还将HystrixCommand上的执行超时设置为10,000 ms,从而允许远程服务有足够的时间进行响应。
- 现在让我们看看当执行超时小于服务超时调用时会发生什么
@Test
public void givenSvcTimeoutOf15000AndExecTimeoutOf5000_whenRemoteSvcExecuted_thenExpectHre()
throws InterruptedException {
HystrixCommand.Setter config = HystrixCommand
.Setter
// 1. 设置组的名称
.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceGroupTest5"));
HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter();
// 2. 设置超时时间
commandProperties.withExecutionTimeoutInMilliseconds(5_000);
config.andCommandPropertiesDefaults(commandProperties);
new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(15_000)).execute();
}
如果远程服务的响应时间超过了我们设置的超时时间 , 我们会发现 , 就会报错
我们期望服务在5000毫秒内响应,而我们将服务设置为在15000毫秒后响应。
如果您在执行测试时注意到,测试将在5000毫秒后退出,而不是等待15000毫秒,并且会抛出一个HystrixRuntimeException。
这演示了Hystrix等待响应的时间不会超过配置的超时时间。这有助于使Hystrix保护的系统反应更灵敏。
下面我们将研究如何设置线程池大小以防止线程耗尽,并讨论它的好处。
有限线程池的防御性编程
设置服务调用的超时并不能解决与远程服务相关的所有问题。
当远程服务开始缓慢响应时,典型的应用程序将继续调用该远程服务。
应用程序不知道远程服务是否正常,并且每次请求进入时都会生成新的线程。
这将导致已经处于挣扎状态的服务器上的线程被使用。
我们不希望这种情况发生,因为我们需要这些线程用于其他远程调用或服务器上运行的进程,我们也希望避免CPU利用率激增。
让我们看看如何在HystrixCommand中设置线程池大小。
@Test
public void givenSvcTimeoutOf500AndExecTimeoutOf10000AndThreadPool_whenRemoteSvcExecuted
_thenReturnSuccess() throws InterruptedException {
HystrixCommand.Setter config = HystrixCommand
.Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceGroupThreadPool"));
HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter();
commandProperties.withExecutionTimeoutInMilliseconds(10_000);
config.andCommandPropertiesDefaults(commandProperties);
config.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
// 1. 最大队列大小
.withMaxQueueSize(10)
// 2. 核心队列大小
.withCoreSize(3)
// 3. 队列拒绝队列大小
.withQueueSizeRejectionThreshold(10));
assertThat(new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(500)).execute(),
equalTo("Success"));
}
在上面的测试中,我们设置了最大队列大小、核心队列大小和队列拒绝大小。
当最大线程数达到10并且任务队列的大小达到10时,Hystrix将开始拒绝请求。
核心大小是线程池中始终保持活动状态的线程数。
具有短路断路器模式的防御性编程
当一个远程服务出现调用失败的情况下
我们不想一直向它发出请求,浪费资源。
理想情况下,我们希望在一段时间内停止发出请求,以便在恢复请求之前给服务恢复的时间。
这就是所谓的短路断路器模式。让我们看看Hystrix是如何实现这个模式的:
@Test
void contextLoads() throws Exception {
HystrixCommand.Setter config = HystrixCommand.
Setter.
// 1 : 设置组的名字
withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceGroupCircuitBreaker"));
HystrixCommandProperties.Setter properties = HystrixCommandProperties.Setter();
// 2 : 执行超时时间 (毫秒)
properties.withExecutionTimeoutInMilliseconds(1000);
// 3 : 断路器睡眠的时间 (毫秒) (如果远程服务出现错误,设置服务的恢复时间)
properties.withCircuitBreakerSleepWindowInMilliseconds(4000);
// 4 : 设置 执行隔离策略 (线程)
properties.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD);
// 5 : 断路器是否开启
properties.withCircuitBreakerEnabled(true);
// 6 : 断路器请求数量阀值
properties.withCircuitBreakerRequestVolumeThreshold(1);
config.andCommandPropertiesDefaults(properties);
// 7. 设置线程池的属性
config.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
// 7.1 : 最大队列
.withMaxQueueSize(1)
// 7.2 : 核心队列
.withCoreSize(1)
// 7.3 : 队列拒绝队列大小
.withQueueSizeRejectionThreshold(1));
System.out.println(invokeRemoteService(config, 1000));
System.out.println(invokeRemoteService(config, 1000));
System.out.println(invokeRemoteService(config, 10));
Thread.sleep(5000);
System.out.println(new CommandHelloWorld(config, new RemoteServiceTestSimulator(500)).execute());
System.out.println(new CommandHelloWorld(config, new RemoteServiceTestSimulator(500)).execute());
System.out.println(new CommandHelloWorld(config, new RemoteServiceTestSimulator(500)).execute());
}
public String invokeRemoteService(HystrixCommand.Setter config, int timeout)
throws InterruptedException {
String response = null;
try {
response = new CommandHelloWorld(config,
new RemoteServiceTestSimulator(timeout)).execute();
} catch (HystrixRuntimeException ex) {
System.out.println("ex = " + ex);
}
return response;
}
在上述测试中,我们设置了不同的断路器性能。最重要的是
- CircuitBreakerSleepWindow设置为4000毫秒。这将配置断路器窗口并定义对远程服务的请求将被恢复的时间间隔
- CircuitBreakerRequestVolumeThreshold设置为1,并定义了在考虑故障率之前所需的最小请求数
有了上述设置,我们的HystrixCommand现在将在两次请求失败后打开。第三个请求甚至不会击中远程服务,即使我们已经将服务延迟设置为500ms, Hystrix将短路,我们的方法将返回null作为响应
本人对 Hystrix 做了封装
路径 : https://gitee.com/happy-youth/SpringCloudFamilyMeals/tree/master/springBoot-tool/src/main/java/com/example/springboottool/hystrix
使用方法
- ConnectorEnum 枚举增加远程服务接口
- 注入 HystrixService 对象
- 调用 HystrixService对象的 invokeRemoteService() 方法 , 传入远程服务的接口以及参数,调用远程服务
@Resource
HystrixService hystrixService;
@Test
void test() {
JSONObject jsonObject = new JSONObject();
jsonObject.put("name", "张三");
JSONObject jsonObject1 = hystrixService.invokeRemoteService(ConnectorEnum.TEST, jsonObject);
// # 请求返回值
System.out.println(jsonObject1);
}