一、背景
1. 当前问题
一个系统,所有请求共用同一个APP容器(Tomcat/jetty/等),共用一个用户线程池,依赖多个不同的远程服务。
当系统健康时,处理请求的延时较低,服务正常运行;当某个后端依赖项变得延迟,会导致处理该请求的用户线程长时间阻塞。在流量较低时,只会影响请求本身;在高流量的情况下,单个后端依赖项的延迟可能会导致服务的所有用户线程都在阻塞等待。
这个问题不仅导致与该后端依赖项有关的请求没办法被正常处理,还会导致其他请求拿不到所需资源而出现异常,从而导致整个系统都没办法正常运行。
对于上述问题,需要通过手段对故障、延迟进行隔离和管理,以便单个失败的依赖项不会导致整个应用程序或系统崩溃。
2. Hystrix简介
Hystrix是Netflix开源的容错库,旨在处理分布式系统中的故障和延迟。它通过实现断路器模式、请求缓存、请求合并等功能,提供了弹性和可靠的解决方案。Hystrix能够在服务之间进行隔离,当一个服务出现故障时,它可以防止故障的扩散,提高系统的可用性和稳定性。在微服务架构中,一个请求需要调用多个服务是非常常见的,较底层的服务如果出现故障,会导致连锁故障。当对特定的服务的调用的不可用达到一个阈值(Hystrix是5秒20次)断路器将会被打开。断路打开后,可以避免连锁故障,通过fallback方法快速返回一个值。
3. Hystrix的目标
Hystrix旨在实现以下目标:
- 保护并控制通过第三方客户端库访问(通常是通过网络)的依赖项的延迟和故障。
- 阻止复杂分布式系统中的级联故障。
- 快速失败并快速恢复。
- 在可能的情况下回退并优雅降级。
- 启用近乎实时的监控、警报和操作控制。
4. Hystrix的功能
为了实现上述目标,Hystrix提供了以下功能:
- 用
HystrixCommand
或HystrixObservableCommand
对象包装对所有外部系统(或"依赖项")的调用,通常在单独的线程中执行 - 自定义调用依赖项的超时时间
- 为每个依赖项维护一个小的线程池(或信号量);如果它变满,将要发送到该依赖项的请求立即被拒绝,而不是排队等待
- 统计成功、失败(客户端抛出的异常)、超时和线程拒绝的数量
- 可通过手动或自动的方式,开启断路器以停止一段时间内对特定服务的所有请求;当统计的失败率超过阈值,断路器会自动开启
- 在请求失败、被拒绝、超时或短路时执行fallback逻辑
- 近实时地监控指标和配置更改
二、原理介绍
1. Hystrix执行流程
下图展示了通过Hystrix请求远程服务的流程
1.1 创建HystrixCommand
/HystrixObservableCommand
对象
第1步,创建一个HystrixCommand
或HystrixObservableCommand
包装远程调用依赖的过程。HystrixCommand
和HystrixObservableCommand
的区别后续介绍。
1.2 执行Command
第2步,执行第一步创建的命令Command
,可以使用以下四种方式:execute()
、queue()
、observe()
、toObservable()
,区别后续介绍
1.3 判断是否有请求缓存
第3步,判断是否有请求缓存,若存在请求缓存则直接将缓存的值返回,请求缓存的用法后续介绍。
1.4 判断是否开启断路
第4步,判断断路器是否开启,开启则不能执行远程调用的操作,直接到第8步;若未开启,则到第5步。第4步的断路器开启与否,是通过第7步的断路器健康值判断的。
1.5 判断线程池/信号量是否已满
第5步,如果与命令关联的线程池和队列(或信号量)已满,那么Hystrix将不会执行该命令,而是立即将流量路由到(8)获取Fallback。第5步的结果会反馈到第7步的断路器健康值中。
1.6 执行远程调用
第6步,执行远程调用的方法。在第1步构造的HystrixCommand
或HystrixObservableCommand
中,会重写HystrixCommand.run()
和HystrixObservableCommand.construct()
,里面执行远程调用过程。
如果执行失败/超时,则会路由到第8步执行fallback方法。
值得注意的是,执行超时时,线程隔离策略下定时器会抛出超时异常,并且通知执行任务的线程中断;信号量隔离策略下,定时器会抛出超时异常,但是执行任务的线程会执行到任务结束。
1.7 计算断路器的健康度
第7步,Hystrix向断路器报告成功、失败、拒绝和超时,断路器维护一组滚动计数器来计算统计数据。
它使用这些统计数据来确定何时应该断路,此时它会拒绝任何后续请求,直到恢复期过去,然后它会让会进入半开状态。在此状态下,下一个请求将尝试调用后端服务。如果该请求成功,断路器会认为服务已经恢复并转回 关闭 状态;如果请求仍然失败,断路器会再次回到 打开 状态,继续进行短路操作。
1.8 获取并执行fallback
第8步,当第4步发现断路器打开、第5步线程池/信号量已满、第6步执行异常/超时,Hystrix就会尝试获取Command中自定义的备用方法getFallback()。建议该方法不依赖任何网络连接,而是从内存缓存或其他静态逻辑中获取。
如果fallback执行异常或者没有重写getFallback()方法,则会抛出异常。
1.9 返回成功的响应
如果执行成功,则会将远程调用的结果返回给调用者。
2. 断路器工作流程
下图展示了HystrixCommand和断路器的交互流程以及断路器的工作流程:
- 当执行HystrixCommand时,会调用断路器查询是否允许请求。断路器会查询断路器是否开启,若没开启,直接返回允许请求;若断路器已开启,会判断恢复时间是否已过,已过允许1个请求,未过返回不允许请求。(对应2.1的第4步)
- 判断断路器是否开启的方式时,计算时间范围内错误百分比是否超过阈值,如果超过阈值,则返回已开启。
- 当执行HystrixCommand之后,会将结果反馈给断路器,以更新断路器的健康度。
3、 隔离策略
Hystrix 使用舱壁模式来隔离依赖关系,并限制对任何单一依赖关系的并发访问。
3.1 线程池隔离(THREAD)
在此策略中,每个服务调用都在独立的线程中执行。线程隔离可以防止服务调用时间过长,导致其他服务调用受到影响。当一个服务调用超时或发生故障时,它不会影响到其他服务调用。线程隔离可以确保服务之间的独立性,提高系统的稳定性。
3.1.1 优势
- 该应用程序完全不受失控客户端库的影响。给定依赖库的池可以填满,而不会影响应用程序的其余部分。
- 支持任务排队
- 支持超时中断
- 支持异步调用
3.1.2 劣势
- 线程调用会产生额外的开销
3.1.3 适用场景
- 对于可能耗时较长、网络延迟较高的外部服务调用。
- 当需要确保每个命令在独立的线程上运行以防止阻塞主线程。
- 为了防止故障传播和资源耗尽,需要对每个命令进行严格的资源限制。
- 能够承受一定的线程创建和销毁的开销,以换取更稳定的系统行为。
- 适用于处理高负载和突发流量的情况,因为线程池可以帮助稳定系统的整体性能。
3.2 信号量隔离(SEMAPHORE)
信号量隔离是一种基于计数器的轻量级隔离方法,它不创建新的线程。相反,Hystrix 使用一个指定大小的信号量来控制并发访问的数量。一旦达到最大值,任何额外的请求都将被拒绝并触发降级策略。
3.2.1 优势
- 轻量,无额外开销
3.2.2 劣势
- 不支持任务排队
- 不支持超时中断。执行超时,执行线程会继续阻塞,直到底层的网络调用超时
- 不支持异步调用。实际上还是所有请求共用用户线程,没办法完全隔离。
3.2.3 适用场景
- 对性能敏感或低延迟要求的应用。
- 当调用的服务通常是快速响应的,例如查询缓存服务或者内部服务间通信。
- 调用的依赖服务不需要消耗大量的 CPU 或者 IO 资源。
- 需要严格控制并发请求的数量,但又不希望引入额外的线程开销。
- 在某些情况下,可以用于轻量级的微服务之间的一致性保证。
三. 使用介绍
本章节将介绍hystrix提供的原始API、Spring Cloud集成Hystrix的使用
1. Hystrix使用
1.1 依赖
xml
<!-- https://mvnrepository.com/artifact/com.netflix.hystrix/hystrix-core -->
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>1.5.18</version>
</dependency>
1.2 Hello World
下面是创建一个HystrixCommand
的方式,继承HystrixCommand
,泛型为远程调用响应的类型。
构造方法中指定了该命令所属的分组key,Hystrix会基于分组key用于报告、告警、仪表盘、权限控制等,并且在不指定线程池的情况下,会根据这个key命名线程池。(也就是说,相同key共用同一个线程池)
run()
方法中,是真正执行远程调用的位置。
java
public class CommandHelloWorld extends HystrixCommand<String> {
private final String name;
public CommandHelloWorld(String name) {
// 指定命令分组为ExampleGroup
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name;
}
@Override
protected String run() {
// 真正执行远程调用的位置
return "Hello " + name + "!";
}
}
执行HystrixCommand
,同步等待结果
java
String s = new CommandHelloWorld("World").execute();
执行HystrixCommand
,异步等待结果。
java
Future<String> fs = new CommandHelloWorld("World").queue();
String s = fs.get();
响应式执行HystrixCommand
,observe()
将HystrixCommand
转换为Observable
,并通过subscribe()
设置订阅者,设置当处理结果、处理异常、出现新元素时的行为。
java
Observable<String> ho = new CommandHelloWorld("World").observe();
ho.subscribe(new Subscriber<String>() {
@Override
public void onCompleted() {
LOGGER.info("Completed.");
}
@Override
public void onError(Throwable e) {
LOGGER.error("Error ", e);
}
@Override
public void onNext(String s) {
// 获取结果
LOGGER.info("Next {}", s);
}
});
除HystrixCommand
以外,还可以创建一个 HystrixObservableCommand
。这是一种专门用于包装 Observables 的 HystrixCommand
版本。HystrixObservableCommand
可以包装发射(emit)多个元素的 Observable
,而普通的 HystrixCommands
(即使转换为 Observable
)也永远不会发射(emit)超过一个元素。
1.3 Fallback
可以通过实现Fallback方法,当执行任务出现超时/异常时优雅地降级,返回一个默认值或用于表示错误的值。
对于HystrixCommand
,需要实现getFallback()
方法, 当出现失败、超时、线程池/信号量拒绝、断路器开启时,会执行getFallback()
方法。对于调用方,最终会收到getFallback()
的出参。
java
public class CommandHelloFailure extends HystrixCommand<String> {
private final String name;
public CommandHelloFailure(String name) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name;
}
@Override
protected String run() {
throw new RuntimeException("this command always fails");
}
@Override
protected String getFallback() {
return "Hello Failure " + name + "!";
}
}
在没有实现getFallback()
时,HystrixCommand
会抛出HystrixRuntimeException
给调用方。
无论实现getFallback()
与否,只要失败都会在断路器中计入失败。
值得注意的是,如果想在出现错误之后不执行getFallback()
,可以抛出HystrixBadRequestException
1.4 Command Key
通过HystrixCommandKey
标识HystrixCommand
。当不设置是默认为类名getClass().getSimpleName();
java
public CommandHelloWorld(String name) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld")));
this.name = name;
}
1.5 线程池
可以通过HystrixThreadPoolKey
指定线程池的名称,否则默认使用HystrixCommandGroupKey
使用HystrixThreadPoolKey
而不是单纯使用HystrixCommandGroupKey
的原因可能是有一些HystrixCommand
确实是在权限或者逻辑功能上是同一个组的,但是需要互相隔离。
java
public CommandHelloWorld(String name) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("HelloWorldPool")));// 指定线程池的名称为HelloWorldPool
this.name = name;
}
1.5 隔离策略
通过以下的方式可以指定隔离策略
java
protected CommandIsolationStrategy(String name) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD))
//.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE))
);
this.name = name;
}
其他参数配置与上述方式基本一致,在此不再赘述
1.6 配置
每个配置参数,有四个优先级,越往后的优先级越高。
1.6.1 代码全局默认值
Hystrix自带的配置参数的默认值
1.6.2 动态配置全局默认值
可修改Java系统参数修改默认值。通过代码设置或者在启动服务时指定
java
System.setProperty("hystrix.command.default.execution.isolation.strategy", "SEMAPHORE");
shell
java -Dhystrix.command.default.execution.isolation.strategy=SEMAPHORE ...
其中,default
说明是全局默认值
1.6.3 代码设置单体值
即上述几个小节在创建HystrixCommand
的设置方式
1.6.4 动态配置单体值
与3.1.7.2一样,通过修改系统参数实现。
java
System.setProperty("hystrix.command.HystrixCommandKey.execution.isolation.strategy", "THREAD");
shell
java -Dhystrix.command.HystrixCommandKey.execution.isolation.strategy=THREAD ...
其中,HystrixCommandKey
修改为HystrixCommand
的对应值
更多参数有关的内容,见第4章
2. Spring Cloud使用
本小节将介绍Hystrix在Spring Cloud中如何使用。主要介绍两种方式,Spring Cloud OpenFeign 集成Hystrix、Spring Cloud Netflix Hystrix
2.1 Spring Cloud OpenFeign集成Hystrix
本小节仅介绍简单的使用,若想了解OpenFeign集成Hystrix的原理,可参考源码feign.hystrix.HystrixInvocationHandler
(从源码中可以看到,其实就是通过动态代理的方式,用HystrixCommand将FeignClient远程调用的方法包装起来)
2.1.1 依赖
只需引入spring-cloud-starter-openfeign
即可。
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
我们点击进spring-cloud-starter-openfeign
的pom文件中可以看到它已引入feign-hystrix
,它将Hystrix集成到Feign中。
xml
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-hystrix</artifactId>
<version>10.10.1</version>
<scope>compile</scope>
</dependency>
(这个依赖不需要单独引)
2.1.2 配置
首先,划重点,配置必须开启feign-hystrix,否则hystrix默认是不开启的。详见OpenFeign源码org.springframework.cloud.openfeign.FeignClientsConfiguration.HystrixFeignConfiguration
yml
feign:
hystrix:
enabled: true
其次,如果需要修改Hystrix的配置参数,可通过修改配置实现。比如,修改默认隔离策略:
yaml
hystrix:
command:
default:
execution:
isolation:
strategy: THREAD
key和value与Hystrix原生的保持一致,详见第4章。
需要说明的是,feignClient的每一个方法都是一个HystrixCommand,HystrixCommandKey为简单类名#方法名(字段类型...)
。
比如说下面的FeignClient
的runsWell
的HystrixCommandKey为HystrixService#runsWell(Integer)
、runs
的HystrixCommandKey为HystrixService#runs()
因此,根据第4章介绍的配置方法,如果想细粒度地修改指定的HystrixCommand的配置,需要指定对应的HystrixCommandKey。
yml
hystrix:
command:
HystrixService#runsWell(Integer):
execution:
isolation:
strategy: SEMAPHORE
HystrixService#runs():
execution:
isolation:
strategy: THREAD
java
@FeignClient(value = "HystrixCommandGroupKey")
public interface ExampleApi {
@GetMapping("/hystrix/ok/{id}")
String runsWell(@PathVariable("id") Integer id);
@GetMapping("/hystrix/ok")
String runs();
}
另外,还有一个点值得提一下,@FeignClient上可以通过name
/value
指定远程RPC的服务,同时会将它作为HystrixCommandGroupKey
,也就是说在不特殊配置的情况下,无论创建多少个相同服务的FeignClient
,它们都会在同一个分组下,并且会使用相同的线程池。
2.1.3 Fallback
Fallback比较简单,在@FeignClient
上指定fallbackFactory
,并且实现一个FallbackFactory
即可。
java
@FeignClient(name = "base-cloud-for-biz", url = "${base-cloud.url}",
fallbackFactory = BaseCloudClientForBizFallbackFactory.class)
public interface BaseCloudClientForBiz {
@PostMapping(path="/api/v2/internal/sendSMSWithoutRegister",
produces = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE)
ApiResult sendSmsWithoutRegister(SendSmsWithoutRegisterDto dto);
}
java
@Slf4j
@Component
public class BaseCloudClientForBizFallbackFactory implements FallbackFactory<BaseCloudClientForBiz> {
private static final String ERROR_LOG = "internal service api is unavailable, api = {}, request data : {}.";
@Override
public BaseCloudClientForBiz create(Throwable throwable) {
return new BaseCloudClientForBiz() {
@Override
public ApiResult sendSmsWithoutRegister(SendSmsWithoutRegisterDto dto) {
log.warn(ERROR_LOG, "sendSmsWithoutRegister", dto);
return ApiResult.SYSTEM_INTERNAL_ERROR;
}
};
}
}
2.2 Spring Cloud Netflix Hystrix
除了OpenFeign之外,可能还会有其他调用远程依赖的方法,我们可以通过spring-cloud-starter-netflix-hystrix
引入Hystrix
。它支持我们通过注解的方式实现HystrixCommand
的创建、配置。实现原理可参考com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect
(简单来说就是通过AOP包装远程调用的方法)
2.2.1 依赖
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2.2.2 启动
需要在配置类上加上注解EnableHystrix
java
@EnableHystrix
public class Application {
}
2.2.3 使用及配置
使用比较简单,在方法上添加注解@HystrixCommand
即可
java
@HystrixCommand(fallbackMethod = "exampleFallback",
commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")})
@GetMapping("/example")
public String example() {
return "";
}
public String exampleFallback() {
return "";
}
可以看出,字段与3.1的用法基本相同
java
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface HystrixCommand {
String groupKey() default "";
String commandKey() default "";
String threadPoolKey() default "";
String fallbackMethod() default "";
HystrixProperty[] commandProperties() default {};
HystrixProperty[] threadPoolProperties() default {};
Class<? extends Throwable>[] ignoreExceptions() default {};
ObservableExecutionMode observableExecutionMode() default ObservableExecutionMode.EAGER;
HystrixException[] raiseHystrixExceptions() default {};
String defaultFallback() default "";
}
四、参数配置
1. Execution
配置 | 参数名 | 默认值 | 备注 |
---|---|---|---|
隔离策略 | hystrix.command.default.execution.isolation.strategy hystrix.command.HystrixCommandKey.execution.isolation.strategy | THREAD | 可选:THREAD, SEMAPHORE |
命令执行超时时间(毫秒) | hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds hystrix.command.HystrixCommandKey.execution.isolation.thread.timeoutInMilliseconds | 1000 | execution.timeout.enabled=true生效 |
执行超时开关 | hystrix.command.default.execution.timeout.enabled hystrix.command.HystrixCommandKey.execution.timeout.enabled | true | |
超时中断开关 | hystrix.command.default.execution.isolation.thread.interruptOnTimeout hystrix.command.HystrixCommandKey.execution.isolation.thread.interruptOnTimeout | true | |
信号量最高并发请求数 | hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests hystrix.command.HystrixCommandKey.execution.isolation.semaphore.maxConcurrentRequests | 10 |
2. Fallback
配置 | 参数名 | 默认值 | 备注 |
---|---|---|---|
fallback开关 | hystrix.command.default.fallback.enabled hystrix.command.HystrixCommandKey.fallback.enabled | true |
3. Circuit Breaker
配置 | 参数名 | 默认值 | 备注 |
---|---|---|---|
断路器开关 | hystrix.command.default.circuitBreaker.enabled hystrix.command.HystrixCommandKey.circuitBreaker.enabled | true | |
断路最小阈值 | hystrix.command.default.circuitBreaker.requestVolumeThreshold hystrix.command.HystrixCommandKey.circuitBreaker.requestVolumeThreshold | 20 | 只有滑动时间窗口内的请求数超过阈值,才会有机会触发断路 |
断路睡眠时间(毫秒) | hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds hystrix.command.HystrixCommandKey.circuitBreaker.sleepWindowInMilliseconds | 5000 | 断路之后的睡眠时长,在此时间内拒绝任何请求 |
断路错误率阈值 | hystrix.command.default.circuitBreaker.errorThresholdPercentage hystrix.command.HystrixCommandKey.circuitBreaker.errorThresholdPercentage | 50 | 错误率超过阈值触发断路 |
4. Metrics
配置 | 参数名 | 默认值 | 备注 |
---|---|---|---|
滑动统计时间长度(毫秒) | hystrix.command.default.metrics.rollingStats.timeInMilliseconds hystrix.command.HystrixCommandKey.metrics.rollingStats.timeInMilliseconds | 10000 | |
滑动统计桶数量 | hystrix.command.default.metrics.rollingStats.numBuckets hystrix.command.HystrixCommandKey.metrics.rollingStats.numBuckets | 10 | 注意:timeInMilliseconds%numBuckets必须等于0 |
统计延迟开关 | hystrix.command.default.metrics.rollingPercentile.enabled hystrix.command.HystrixCommandKey.metrics.rollingPercentile.enabled | true | 若设置为false,则延迟的情况进行统计 |
5. Thread Pool
配置 | 参数名 | 默认值 | 备注 |
---|---|---|---|
线程池核心线程数 | hystrix.threadpool.default.coreSize hystrix.threadpool.HystrixThreadPoolKey.coreSize | 10 | |
线程池最大线程数 | hystrix.threadpool.default.maximumSize hystrix.threadpool.HystrixThreadPoolKey.maximumSize | 10 | |
线程池队列最大长度 | hystrix.threadpool.default.maxQueueSize hystrix.threadpool.HystrixThreadPoolKey.maxQueueSize | -1 | 如果设置-1,则使用SynchronousQueue队列 如果设置正数,则使用LinkedBlockingQueue队列 |
空闲线程存活时长(分钟) | hystrix.threadpool.default.keepAliveTimeMinutes hystrix.threadpool.HystrixThreadPoolKey.keepAliveTimeMinutes | 1 |
线程池计算公式:
线程数=最高峰时每秒的请求数量 × 99%命令执行时间 + 喘息空间
五、常见模式
1. Fail Fast
java
public class CommandThatFailsFast extends HystrixCommand<String> {
private final boolean throwException;
public CommandThatFailsFast(boolean throwException) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.throwException = throwException;
}
@Override
protected String run() {
if (throwException) {
throw new RuntimeException("failure from CommandThatFailsFast");
} else {
return "success";
}
}
2. Fail Silent
java
public class CommandThatFailsSilently extends HystrixCommand<String> {
private final boolean throwException;
public CommandThatFailsSilently(boolean throwException) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.throwException = throwException;
}
@Override
protected String run() {
if (throwException) {
throw new RuntimeException("failure from CommandThatFailsFast");
} else {
return "success";
}
}
@Override
protected String getFallback() {
return null;
}
}
3. Fallback: Static
java
@Override
protected Boolean getFallback() {
return true;
}
4. Fallback via Network
java
public class CommandWithFallbackViaNetwork extends HystrixCommand<String> {
private final int id;
protected CommandWithFallbackViaNetwork(int id) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceX"))
.andCommandKey(HystrixCommandKey.Factory.asKey("GetValueCommand")));
this.id = id;
}
@Override
protected String run() {
// RemoteServiceXClient.getValue(id);
throw new RuntimeException("force failure for example");
}
@Override
protected String getFallback() {
return new FallbackViaNetwork(id).execute();
}
private static class FallbackViaNetwork extends HystrixCommand<String> {
private final int id;
public FallbackViaNetwork(int id) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceX"))
.andCommandKey(HystrixCommandKey.Factory.asKey("GetValueFallbackCommand"))
// use a different threadpool for the fallback command
// so saturating the RemoteServiceX pool won't prevent
// fallbacks from executing
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("RemoteServiceXFallback")));
this.id = id;
}
@Override
protected String run() {
MemCacheClient.getValue(id);
}
@Override
protected String getFallback() {
// the fallback also failed
// so this fallback-of-a-fallback will
// fail silently and return null
return null;
}
}
}
5. Primary + Secondary with Fallback
六、总结
- Hystrix是一个用于分布式系统故障隔离和控制的开源库。它通过断路器模式、请求缓存、请求合并等功能,提供了弹性和可靠的解决方案。Hystrix能够保护并控制对远程服务的调用,避免级联故障,并快速失败并快速恢复。
- Hystrix支持两种隔离策略,线程池隔离适合远程调用耗时比较长的场景,但会有一定的性能损耗;信号量适合低耗时、对性能敏感的场景,缺点是不支持异步调用、不支持中断。
- 可以通过
hystrix-core
使用原生的Hystrix,也可以通过Spring Cloud OpenFeign
或者Spring Cloud Netflix Hystrix
使用。 - 可以根据实际情况修改Hystrix的配置,可以参考常见的几种模式使用HystrixCommand。