专栏系列文章:SpringCloud系列专栏java
系列文章:mysql
SpringCloud 源码系列(1)— 注册中心Eureka 之 启动初始化git
SpringCloud 源码系列(2)— 注册中心Eureka 之 服务注册、续约github
SpringCloud 源码系列(3)— 注册中心Eureka 之 抓取注册表web
SpringCloud 源码系列(4)— 注册中心Eureka 之 服务下线、故障、自我保护机制redis
SpringCloud 源码系列(5)— 注册中心Eureka 之 EurekaServer集群spring
SpringCloud 源码系列(6)— 注册中心Eureka 之 总结篇sql
SpringCloud 源码系列(7)— 负载均衡Ribbon 之 RestTemplate数据库
SpringCloud 源码系列(8)— 负载均衡Ribbon 之 核心原理缓存
SpringCloud 源码系列(9)— 负载均衡Ribbon 之 核心组件与配置
SpringCloud 源码系列(10)— 负载均衡Ribbon 之 HTTP客户端组件
SpringCloud 源码系列(11)— 负载均衡Ribbon 之 重试与总结篇
SpringCloud 源码系列(12)— 服务调用Feign 之 基础使用篇
SpringCloud 源码系列(13)— 服务调用Feign 之 扫描@FeignClient注解接口
SpringCloud 源码系列(14)— 服务调用Feign 之 构建@FeignClient接口动态代理
SpringCloud 源码系列(15)— 服务调用Feign 之 结合Ribbon进行负载均衡请求
在分布式系统中,服务与服务之间的依赖错综复杂,某些服务出现故障,可能致使依赖于它们的其余服务出现级联阻塞故障。
例以下图,某个请求要调用 Service-A,Service-A 又要调用 Service-B、Service-D,Service-B 又要再调用 Service-C,Service-C 又依赖于数据库、Redis等中间件。若是在调用 Service-C 时,因为 Service-C 自己业务问题或数据库宕机等状况,就可能会致使 Service-B 的工做线程所有占满致使不可用,进而又致使级联的 Service-A 资源耗尽不可用,这时就会致使整个系统出现大面积的延迟或瘫痪。
在高并发的状况下,单个服务的延迟会致使整个请求都处于延迟阻塞状态,最终的结果就是整个服务的线程资源消耗殆尽。服务的依赖性会致使依赖于该故障服务的其余服务也处于线程阻塞状态,最终致使这些服务的线程资源消耗殆尽,直到不可用,从而致使整个微服务系统都不可用,即雪崩效应。
例如,对于依赖 30 个服务的应用程序,每一个服务的正常运行时间为 99.99%,对于单个服务来讲,99.99% 的可用是很是完美的。有 99.99^30 = 99.7% 的可正常运行时间和 0.3% 的不可用时间,那么 10 亿次请求中就有 3000000 次失败,实际的状况可能比这更糟糕。
若是不设计整个系统的韧性,即便全部依赖关系表现良好,单个服务只有 0.01% 的不可用,因为整个系统的服务相互依赖,最终对整个系统的影响是很是大的。
一、简介
Hystrix是由Netflix开源的一个针对分布式系统容错处理的开源组件,旨在隔离远程系统、服务和第三方库,阻止级联故障,在复杂的分布式系统中实现恢复能力,从而提升了整个分布式系统的弹性。
例如在上图中,若是在 Service-B 调用 Service-C 时,引入 Hystrix 进行资源隔离,Service-C 故障或调用超时就自动降级返回,从而隔离了 Service-C 对 Service-B 的级联影响,进而保证了整个系统的稳定性。
二、Hystrix的设计原则
总的来讲Hystrix 的设计原则以下:
三、官方文档
GitHub:github.com/Netflix/Hys…
注意:Hystrix 再也不开发新功能,目前处于维护模式,最新稳定版本是 1.5.18
。
使用前在 spring cloud 项目中引入 netflix 相关依赖:我本地使用的 spring-cloud-netflix-hystrix
版本为 2.2.5.RELEASE
。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
复制代码
四、参考书籍:
Hystrix的工做机制能够参考官方【How-it-Works】。下面这幅图展现了 Hystrix 的核心工做原理。
核心的原理以下:
① 将对依赖服务的请求调用封装到 HystrixCommand
或者 HystrixObservableCommand
中,这个访问请求通常会在独立的线程中执行。区别在于:
② 执行请求,有四个方法能够调用:execute()、queue()、observe()、toObservable()
,HystrixCommand 四个均可以调用,HystrixObservableCommand 只能调用 observe()
或 toObservable()
。
③ 若是这个命令启用了缓存,且缓存中存在,就直接返回缓存中的数据。
④ 检查断路器是否打开了,若是断路器打开了就直接走快速失败的逻辑返回。
⑤ 检查线程池(线程池隔离)或队列(信号量隔离)是否已满,满了就直接走快速失败的逻辑返回。
⑥ 执行请求调用远程服务,若是执行执行失败或超时,就走降级逻辑返回。不然成功返回数据。
⑦ 每次执行请求时,线程池是否拒绝、执行是否失败、超时等,都会进行统计,而后计算是否打开断路器。
⑧ 执行快速失败的逻辑,也就是降级逻辑,若是降级逻辑执行成功,则返回;若是失败须要自行处理,或抛出异常。
关于 HystrixCommand
或者 HystrixObservableCommand
的用法官方文档【How To Use】已经介绍得很详细了,这里就跟着官方文档中提供的例子来了解下 HystrixCommand 的使用方式以及 Hystrix 的特性、配置等。
一、构建 HystrixCommand
继承 HystrixCommand
,将业务逻辑封装到 HystrixCommand 的 run()
方法中,在 getFallback()
方法中实现错误回调的逻辑。
class CommandHello extends HystrixCommand<String> {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final String name;
private final long timeout;
protected CommandHello(String group, String name, long timeout) {
// 指定命令的分組,同一组使用同一个线程池
super(HystrixCommandGroupKey.Factory.asKey(group));
this.name = name;
this.timeout = timeout;
}
// 要封装的业务请求
@Override
protected String run() throws Exception {
logger.info("hystrix command execute");
if (name == null) {
throw new RuntimeException("data exception");
}
Thread.sleep(timeout); // 休眠
return "hello " + name;
}
// 快速失败的降级逻辑
@Override
protected String getFallback() {
logger.info("return fallback data");
return "error";
}
}
复制代码
二、构建 HystrixObservableCommand
继承 HystrixObservableCommand
,将业务逻辑封装到 HystrixObservableCommand 的 construct()
方法中,在 resumeWithFallback()
方法中实现错误回调的逻辑。
class CommandObservableHello extends HystrixObservableCommand<String> {
private final Logger logger = LoggerFactory.getLogger(getClass());
protected CommandObservableHello(String group) {
// 指定命令的分組,同一组使用同一个线程池
super(HystrixCommandGroupKey.Factory.asKey(group));
}
@Override
protected Observable<String> construct() {
logger.info("hystrix command execute");
return Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
// 发送多条数据
subscriber.onNext("hello world");
subscriber.onNext("hello hystrix");
subscriber.onNext("hello command");
subscriber.onCompleted();
}
});
}
// 快速失败的降级逻辑
@Override
protected Observable<String> resumeWithFallback() {
logger.info("return fallback data");
return Observable.just("error");
}
}
复制代码
一、执行请求的方法
每次执行 HystrixCommand 都须要从新建立一个 HystrixCommand 命令,而后调用 execute()
、queue()
、observe()
、toObservable()
中的一个方法执行请求。HystrixCommand 能够调用四个方法,HystrixObservableCommand 只能调用 observe()、toObservable()
两个方法。
四个方法的区别在于:
execute()
:同步调用,直到返回单条结果,或者抛出异常queue()
:异步调用,返回一个 Future
,能够异步的作其它事情,后面能够经过 Future 获取单条结果observe()
:返回一个订阅对象 Observable,当即执行。能够经过 Observable.toBlocking() 执行同步请求。toObservable()
:返回一个 Observable,只有订阅后才会执行进入 execute()
方法能够发现,execute() 调用了 queue().get()
;再进入 queue() 方法能够发现,queue() 实际又调用了 toObservable().toBlocking().toFuture()
,也就是说,不管是哪一种方式执行command,最终都是依赖 toObservable()
去执行的。
二、HystrixCommand 执行请求
能够看到 HystrixCommand 中的业务执行是在一个单独的线程中执行的,线程名称为 hystrix-ExampleGroup-1
,这就实现了资源的隔离了。
/** * 运行结果: * * 14:56:03.699 [hystrix-ExampleGroup-1] INFO com.lyyzoo.hystrix.CommandHello - hystrix command execute * 14:56:04.206 [main] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - execute result is: hello hystrix */
@Test
public void test_HystrixCommand_execute() {
CommandHello command = new CommandHello("ExampleGroup", "hystrix", 500);
// 同步执行
String result = command.execute();
logger.info("execute result is: {}", result);
}
/** * 运行结果: * * 14:56:19.269 [main] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - do something... * 14:56:19.279 [hystrix-ExampleGroup-1] INFO com.lyyzoo.hystrix.CommandHello - hystrix command execute * 14:56:19.785 [main] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - queue result is: hello hystrix */
@Test
public void test_HystrixCommand_queue() throws Exception {
CommandHello command = new CommandHello("ExampleGroup", "hystrix", 500);
// 异步执行,返回 Future
Future<String> future = command.queue();
logger.info("do something...");
logger.info("queue result is: {}", future.get());
}
/** * 运行结果 * * 14:59:56.748 [hystrix-ExampleGroup-1] INFO com.lyyzoo.hystrix.CommandHello - hystrix command execute * 14:59:57.252 [main] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - observe result is: hello hystrix */
@Test
public void test_HystrixCommand_observe_single() {
CommandHello command = new CommandHello("ExampleGroup", "hystrix", 500);
Observable<String> observable = command.observe();
// 获取请求结果,toBlocking() 是为了同步执行,不加 toBlocking() 就是异步执行
String result = observable.toBlocking().single();
logger.info("observe result is: {}", result);
}
/** * 运行结果: * * 15:00:58.921 [hystrix-ExampleGroup-1] INFO com.lyyzoo.hystrix.CommandHello - hystrix command execute * 15:00:59.424 [main] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - subscribe result is: hello hystrix * 15:00:59.425 [main] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - completed */
@Test
public void test_HystrixCommand_observe_subscribe() {
CommandHello command = new CommandHello("ExampleGroup", "hystrix", 500);
Observable<String> observable = command.observe();
// 订阅结果处理
observable.toBlocking().subscribe(new Subscriber<String>() {
@Override
public void onCompleted() {
logger.info("completed");
}
@Override
public void onError(Throwable e) {
logger.info("error", e);
}
@Override
public void onNext(String s) {
logger.info("subscribe result is: {}", s);
}
});
}
复制代码
三、HystrixObservableCommand 执行请求
HystrixObservableCommand 与 HystrixCommand 是相似的,区别在于 HystrixObservableCommand 的业务逻辑是封装到 construct()
方法中,且 HystrixObservableCommand 只能调用 observe()
、toObservable()
两个方法执行命令。
/** * 运行结果: * * 15:22:49.306 [main] INFO com.lyyzoo.hystrix.CommandObservableHello - hystrix command execute * 15:22:49.316 [main] INFO com.lyyzoo.hystrix.Demo02_HystrixObservableCommand - last result: hello command */
@Test
public void test_HystrixObservableCommand_observe() {
CommandObservableHello command = new CommandObservableHello("ExampleGroup");
String result = command.observe().toBlocking().last();
logger.info("last result: {}", result);
}
/** * 运行结果: * * 15:23:08.685 [main] INFO com.lyyzoo.hystrix.CommandObservableHello - hystrix command execute * 15:23:08.691 [main] INFO com.lyyzoo.hystrix.Demo02_HystrixObservableCommand - result data: hello world * 15:23:08.691 [main] INFO com.lyyzoo.hystrix.Demo02_HystrixObservableCommand - result data: hello hystrix * 15:23:08.691 [main] INFO com.lyyzoo.hystrix.Demo02_HystrixObservableCommand - result data: hello command * 15:23:08.691 [main] INFO com.lyyzoo.hystrix.Demo02_HystrixObservableCommand - completed */
@Test
public void test_HystrixObservableCommand_observe_subscribe() {
CommandObservableHello command = new CommandObservableHello("ExampleGroup");
command.observe().subscribe(new Observer<String>() {
@Override
public void onCompleted() {
logger.info("completed");
}
@Override
public void onError(Throwable e) {
logger.info("error");
}
@Override
public void onNext(String o) {
logger.info("result data: {}", o);
}
});
}
复制代码
一、容错模式
HystrixCommand 执行有两种容错模式,fail-fast
、fail-silent
:
fail-fast
:不给fallback降级逻辑,run() 方法报错后直接抛异常,抛出到主工做线程。fail-silent
:给一个fallback降级逻辑,run() 报错了会走fallback降级。基本不会用 fail-fast 模式,通常来讲都会实现降级逻辑,在抛出异常后作一些兜底的事情。
二、实现降级方法
HystrixCommand 能够经过 getFallback()
方法返回降级的数据,HystrixObservableCommand 能够经过 resumeWithFallback()
返回降级的数据。当快速失败、超时、断路器打开的时候,就能够进入降级回调方法中,例如从本地缓存直接返回数据,避免业务异常、阻塞等状况。
经过文档能够了解到,有5中状况会进入降级回调方法中,分别是 业务异常、业务超时、断路器打开、线程池拒绝、信号量拒绝
。
三、Examples:
/** * 运行结果: * * 14:44:43.199 [hystrix-ExampleGroup-1] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - hystrix command execute * 14:44:43.203 [hystrix-ExampleGroup-1] DEBUG com.netflix.hystrix.AbstractCommand - Error executing HystrixCommand.run(). Proceeding to fallback logic ... * java.lang.RuntimeException: data exception * at com.lyyzoo.hystrix.Demo01_HystrixCommand$CommandHello.run(Demo01_HystrixCommand.java:75) * at com.lyyzoo.hystrix.Demo01_HystrixCommand$CommandHello.run(Demo01_HystrixCommand.java:61) * at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:302) * ........ * 14:44:43.210 [hystrix-ExampleGroup-1] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - return fallback data * 14:44:43.214 [main] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - result is: error */
@Test
public void test_HystrixCommand_exception_fallback() {
CommandHello command = new CommandHello("ExampleGroup", null, 500);
// 抛出异常,返回降级逻辑中的数据
String result = command.execute();
logger.info("result is: {}", result);
}
/** * 运行结果: * * 14:51:27.114 [hystrix-ExampleGroup-1] INFO com.lyyzoo.hystrix.CommandHello - hystrix command execute * 14:51:28.113 [HystrixTimer-1] INFO com.lyyzoo.hystrix.CommandHello - return fallback data * 14:51:28.119 [main] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - result is: error */
@Test
public void test_HystrixCommand_timeout_fallback() {
CommandHello command = new CommandHello("ExampleGroup", "hystrix", 1500);
// 请求超时,返回降级逻辑中的数据
String result = command.execute();
logger.info("result is: {}", result);
}
复制代码
四、多级降级
多级降级其实就是在降级逻辑中再嵌套一个 command,command 嵌套 command 的形式。好比先从 MySQL 获取数据,抛出异常时,降级从 redis 获取数据。另外,不一样的降级策略建议使用不一样的线程池,由于若是 command 调用远程服务时耗尽了线程池,降级 command 使用一样的线程池时就会被拒绝。
Examples:
能够看到,首先调用 CommandMySQL,run() 方法正常时就直接返回结果,抛出异常后就进入降级方法中,在降级方法中又用 CommandRedis 执行请求。
public class Demo03_HystrixCommand_MultiFallback {
private final Logger logger = LoggerFactory.getLogger(getClass());
/** * 请求结果: * 22:28:46.313 [hystrix-MySqlPool-1] INFO com.lyyzoo.hystrix.CommandMySQL - get data from mysql * 22:28:46.319 [main] INFO com.lyyzoo.hystrix.Demo03_HystrixCommand_MultiFallback - result: mysql-number-1 * 22:28:46.320 [hystrix-MySqlPool-2] INFO com.lyyzoo.hystrix.CommandMySQL - get data from mysql * 22:28:46.324 [hystrix-MySqlPool-2] DEBUG com.netflix.hystrix.AbstractCommand - Error executing HystrixCommand.run(). Proceeding to fallback logic ... * java.lang.RuntimeException: data not found in mysql * at com.lyyzoo.hystrix.CommandMySQL.run(Demo03_HystrixCommand_MultiFallback.java:50) * at com.lyyzoo.hystrix.CommandMySQL.run(Demo03_HystrixCommand_MultiFallback.java:29) * at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:302) * ...... * 22:28:46.332 [hystrix-MySqlPool-2] INFO com.lyyzoo.hystrix.CommandMySQL - coming mysql fallback * 22:28:46.344 [hystrix-RedisPool-1] INFO com.lyyzoo.hystrix.CommandRedis - get data from redis * 22:28:46.344 [main] INFO com.lyyzoo.hystrix.Demo03_HystrixCommand_MultiFallback - result: redis-number-2 */
@Test
public void test_HystrixCommand_multi_fallback() {
CommandMySQL command = new CommandMySQL("ExampleGroup", 1);
logger.info("result: {}", command.execute());
CommandMySQL command2 = new CommandMySQL("ExampleGroup", 2);
logger.info("result: {}", command2.execute());
}
}
class CommandMySQL extends HystrixCommand<String> {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final String group;
private final Integer id;
public CommandMySQL(String group, Integer id) {
super(
HystrixCommand.Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey(group))
// 指定不一样的线程池
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("MySqlPool"))
);
this.group = group;
this.id = id;
}
@Override
protected String run() throws Exception {
logger.info("get data from mysql");
if (id % 2 == 0) {
throw new RuntimeException("data not found in mysql");
}
return "mysql-number-" + id;
}
// 快速失败的降级逻辑
@Override
protected String getFallback() {
logger.info("coming mysql fallback");
// 嵌套 Command
HystrixCommand<String> command = new CommandRedis(group, id);
return command.execute();
}
}
class CommandRedis extends HystrixCommand<String> {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final Integer id;
public CommandRedis(String group, Integer id) {
super(
HystrixCommand.Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey(group))
// 指定不一样的线程池
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("RedisPool"))
);
this.id = id;
}
@Override
protected String run() throws Exception {
logger.info("get data from redis");
return "redis-number-" + id;
}
// 快速失败的降级逻辑
@Override
protected String getFallback() {
logger.info("coming redis fallback");
return "error";
}
}
复制代码
一、配置分组名称
默认状况下,就是经过 command group 来定义一个线程池,同一个 command group 中的请求,都会进入同一个线程池中,并且还会经过command group来聚合一些监控和报警信息。经过 HystrixCommandGroupKey.Factory.asKey(group)
指定组名。
class CommandHello extends HystrixCommand<String> {
protected CommandHello(String group) {
super(HystrixCommandGroupKey.Factory.asKey(group));
}
}
复制代码
二、配置命令名称
command 名称不配置默认就是类名,能够经过 andCommandKey(HystrixCommandKey.Factory.asKey("CommandName"))
配置。
例如 CommandHystrixConfig:
public CommandHystrixConfig() {
super(
Setter
// 分组名称
.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
// 命令名称,默认为类名 getClass().getSimpleName()
//.andCommandKey(HystrixCommandKey.Factory.asKey("ExampleName"))
);
}
复制代码
未配置名称时,默认就是类名:
将注释放开,就是指定的名称:
三、配置线程池名称
线程池名称默认是分组名称,若是不想用分组名称,能够手动设置线程池名称,配置了线程名称后,经过 .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ExampleThread"))
配置。
public CommandHystrixConfig() {
super(
Setter
// 分组名称
.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
// 命令名称,默认为类名 getClass().getSimpleName()
.andCommandKey(HystrixCommandKey.Factory.asKey("ExampleName"))
// 线程名称
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ExampleThread"))
);
}
复制代码
四、线程池隔离划分
综上来看,一个 command 的执行划分为三个维度:command threadpool -> command group -> command key。
command key
:通常对应到一个依赖服务的接口调用。command group
:通常对应到一个服务的全部接口,包含同一个服务的多个接口,便于对同一个服务的接口调用状况聚合统计。command threadpool
:command 执行的线程池,通常一个 group 对应一个 threadpool,但若是想细粒度拆分,command 能够指定不一样的线程池。Hystrix 核心的一项功能就是资源隔离,要解决的最核心的问题,就是将多个依赖服务的调用分别隔离到各自本身的资源池内,避免对某一个依赖服务的调用,由于依赖服务的接口调用的延迟或者失败,致使服务全部的线程资源所有耗费在这个服务的接口调用上,一旦某个服务的线程资源所有耗尽,就可能致使服务崩溃,甚至故障会不断蔓延。
Hystrix 有线程池隔离
和信号量隔离
两种资源隔离技术:
线程池隔离
:适合绝大多数的场景,依赖服务调用、网络请求,被调用方可能会不可用、超时等,用线程池隔离信号量隔离
:适合不是对外部依赖的访问,而是对内部的一些比较复杂的业务逻辑的访问,不涉及任何的网络请求,只要作信号量的普通限流就能够了,在高并发的状况下作限流。一、启用线程池隔离
public CommandHystrixConfig() {
super(
Setter
// 分组名称
.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
// 设置隔离策略
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
// 超时时间,默认 1000 毫秒
.withExecutionTimeoutInMilliseconds(1000)
// 是否启用降级策略
.withFallbackEnabled(true)
// 是否启用缓存
.withRequestCacheEnabled(true)
)
);
}
复制代码
二、启用信号量隔离
在设置 .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
时,只须要将 THREAD
改成 SEMAPHORE
便可,这个时候 run() 方法将在主线程中执行。
三、资源容量大小控制
public CommandHystrixConfig() {
super(
Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("DemoPool"))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
// 设置隔离策略
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
// 超时时间,默认 1000 毫秒
.withExecutionTimeoutInMilliseconds(1000)
)
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
// 核心线程池大小,默认10个
.withCoreSize(10)
// 最大线程数,默认10个
.withMaximumSize(10)
// 队列数量达到多少后拒绝请求
.withQueueSizeRejectionThreshold(5)
)
);
}
复制代码
public CommandHystrixConfig() {
super(
Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("DemoPool"))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
// 设置隔离策略
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)
// 信号量隔离时最大并发量,默认10
.withExecutionIsolationSemaphoreMaxConcurrentRequests(10)
)
);
}
复制代码
在一次请求上下文中,可能会建立多个 command 去执行远程调用,若是调用的参数同样、接口同样,hystrix 支持缓存来减小执行一样的 command,从而提高性能。
一、开启缓存
开启缓存只须要在 Command 中实现 getCacheKey()
方法返回缓存的 key 就能够开启缓存了。
// 指定缓存的key
@Override
protected String getCacheKey() {
return name;
}
复制代码
二、Examples
Hystrix 的缓存须要本身管理缓存上下文,须要本身初始化缓存上下文 HystrixRequestContext.initializeContext()
,请求结束后须要关闭缓存上下文 HystrixRequestContext.getContextForCurrentThread().shutdown()
。通常在web应用中,能够经过增长前置过滤器来开启 Hystrix 请求上下文,增长后置过滤器来关闭 Hystrix 上下文。
从验证结果来看,最后两个 command 执行时,因为缓存已经存在一样的 key 了,就没有进入 run 方法了,并且能够验证就算是不一样组,只要key相同就会被缓存。
/** * 运行结果: * * 20:55:43.759 [hystrix-ExampleGroup-1] INFO com.lyyzoo.hystrix.CommandHello - hystrix command execute * 20:55:43.878 [main] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - result: hello hystrix * 20:55:43.879 [hystrix-ExampleGroup-2] INFO com.lyyzoo.hystrix.CommandHello - hystrix command execute * 20:55:43.985 [main] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - result: hello ribbon * 20:55:43.989 [main] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - result: hello hystrix * 20:55:43.989 [main] INFO com.lyyzoo.hystrix.Demo01_HystrixCommand - result: hello hystrix */
@Test
public void test_HystrixCommand_cache() {
// 先初始化上下文
HystrixRequestContext context = HystrixRequestContext.initializeContext();
try {
CommandHello command1 = new CommandHello("ExampleGroup", "hystrix", 100);
CommandHello command2 = new CommandHello("ExampleGroup", "ribbon", 100);
CommandHello command3 = new CommandHello("ExampleGroup", "hystrix", 100);
CommandHello command4 = new CommandHello("ExampleGroupTwo", "hystrix", 100);
logger.info("result: {}", command1.execute());
logger.info("result: {}", command2.execute());
logger.info("result: {}", command3.execute());
logger.info("result: {}", command4.execute());
} finally {
//HystrixRequestContext.getContextForCurrentThread().shutdown();
context.shutdown();
}
}
复制代码
三、手动清除缓存
当数据更新时,咱们指望可以手动清理掉缓存,能够经过以下方式清理指定 command 的缓存。
public static void flushCache(String key) {
HystrixRequestCache.getInstance(HystrixCommandKey.Factory.asKey(CommandHello.class.getSimpleName()),
HystrixConcurrencyStrategyDefault.getInstance()).clear(key);
}
复制代码
一、Hystrix 断路器的基本原理以下
HystrixCommandProperties.circuitBreakerRequestVolumeThreshold()
设置,默认为 20。HystrixCommandProperties.circuitBreakerErrorThresholdPercentage()
设置,默认为 50%。CLOSE
状态变为 OPEN
。HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()
设置,默认 5000毫秒;会进入半打开(half-open
)状态,放一个请求过去,若是这个请求仍是失败,断路器就继续维持打开的状态;若是请求成功,就关闭断路器,自动恢复,转到 CLOSE
状态。二、下面用一个 Demo 简单测试下
断路器配置以下:
// 超时时间1000毫秒
.withExecutionTimeoutInMilliseconds(1000)
// 启用断路器
.withCircuitBreakerEnabled(true)
// 限流阈值,超过这个值后才会去判断是否限流,默认20
.withCircuitBreakerRequestVolumeThreshold(4)
// 请求失败百分比阈值,默认50
.withCircuitBreakerErrorThresholdPercentage(50)
// 断路器打开后休眠多久,默认5000毫秒
.withCircuitBreakerSleepWindowInMilliseconds(5000)
复制代码
Examples:
经过测试能够发现,请求阈值设置为4,第五个请求会继续执行,不过也失败了,这个时候就会判断失败比率超过50%了,断路器打开,以后的全部请求就直接进入降级;休眠5秒后,进入半打开状态,放一个请求过去,仍是失败,断路器继续打开。
public class Demo05_HystrixCommand_CircuitBreaker {
@Test
public void test_CircuitBreaker() throws InterruptedException {
for (int i = 1; i <= 10; i++) {
CommandCircuitBreaker command = new CommandCircuitBreaker(1500L, i);
command.execute();
if (i == 7) {
Thread.sleep(5000); // 休眠5秒
}
}
}
}
/** * 运行结果: * * 10:53:30.910 [hystrix-ExampleGroup-1] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [1] execute command * 10:53:31.920 [HystrixTimer-1] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [1] execute fallback * 10:53:31.925 [hystrix-ExampleGroup-2] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [2] execute command * 10:53:32.936 [HystrixTimer-1] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [2] execute fallback * 10:53:32.937 [hystrix-ExampleGroup-3] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [3] execute command * 10:53:33.940 [HystrixTimer-2] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [3] execute fallback * 10:53:33.942 [hystrix-ExampleGroup-4] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [4] execute command * 10:53:34.950 [HystrixTimer-1] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [4] execute fallback * 10:53:34.952 [hystrix-ExampleGroup-5] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [5] execute command * 10:53:35.962 [HystrixTimer-3] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [5] execute fallback * 10:53:35.964 [main] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [6] execute fallback * 10:53:35.965 [main] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [7] execute fallback * 10:53:40.977 [hystrix-ExampleGroup-6] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [8] execute command * 10:53:41.986 [HystrixTimer-2] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [8] execute fallback * 10:53:41.988 [main] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [9] execute fallback * 10:53:41.989 [main] INFO com.lyyzoo.hystrix.CommandCircuitBreaker - [10] execute fallback */
class CommandCircuitBreaker extends HystrixCommand<String> {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final Long timeoutMill;
private final Integer id;
protected CommandCircuitBreaker(Long timeoutMill, Integer id) {
super(
Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
// 超时时间1000毫秒
.withExecutionTimeoutInMilliseconds(1000)
// 启用断路器
.withCircuitBreakerEnabled(true)
// 限流阈值,超过这个值后才会去判断是否限流,默认20
.withCircuitBreakerRequestVolumeThreshold(4)
// 请求失败百分比阈值,默认50
.withCircuitBreakerErrorThresholdPercentage(50)
// 断路器打开后休眠多久,默认5000毫秒
.withCircuitBreakerSleepWindowInMilliseconds(5000)
)
);
this.timeoutMill = timeoutMill;
this.id = id;
}
@Override
protected String run() throws Exception {
logger.info("[{}] execute command", id);
if (timeoutMill != null) {
Thread.sleep(timeoutMill);
}
return "number-" + id;
}
@Override
protected String getFallback() {
logger.info("[{}] execute fallback", id);
return "error-" + id;
}
}
复制代码
在 springboot 程序中,还能够在组件方法上使用 @HystrixCommand
等注解将方法调用封装到 hystrix 中去执行。同时还须要在启动类上加上 @EnableHystrix
来启用 Hystrix 的功能。
@Component
public class ProducerService {
private final Logger logger = LoggerFactory.getLogger(getClass());
@HystrixCommand( groupKey = "ExampleGroup", commandKey = "demo-producer", threadPoolKey = "ExamplePool", fallbackMethod = "queryFallback" )
public String query(Integer id) {
logger.info("execute query");
if (id % 2 == 0) {
throw new RuntimeException("execution error");
}
return "number-" + id;
}
// 回调方法参数要和原方法参数一致
public String queryFallback(Integer id) {
return "error-" + id;
}
}
复制代码
测试结果:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ConsumerApplication.class)
public class ProducerServiceTest {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private ProducerService producerService;
/** * 执行结果: * * INFO 10:49:13 [hystrix-ExamplePool-1] c.l.s.r.h.ProducerService.query 25 :execute query * INFO 10:49:13 [main] c.l.h.ProducerServiceTest.test_query 29 :result: number-1 * INFO 10:49:13 [hystrix-ExamplePool-2] c.l.s.r.h.ProducerService.query 25 :execute query * INFO 10:49:13 [main] c.l.h.ProducerServiceTest.test_query 30 :result: error-2 */
@Test
public void test_query() {
logger.info("result: {}", producerService.query(1));
logger.info("result: {}", producerService.query(2));
}
}
复制代码