压力、挑战,所有“消极的东西”都是成功的催化剂。 代码下载地址:https://github.com/f641385712/netflix-learning
目录- 前言
- 正文
- 触发fallback的5种情况
- 第一种:short-circuited短路
- 示例
- 第二种:threadpool-rejected线程池拒绝
- 示例
- 第三种:semaphore-rejected信号量拒绝
- 示例
- 第四种:timed-out超时
- 示例
- 第五种:failed执行失败
- 示例
- 思考:若fallback方法内执行时抛出异常了呢?
- 总结
- 声明
- 触发fallback的5种情况
- 第一种:short-circuited短路
- 示例
- 第二种:threadpool-rejected线程池拒绝
- 示例
- 第三种:semaphore-rejected信号量拒绝
- 示例
- 第四种:timed-out超时
- 示例
- 第五种:failed执行失败
- 示例
- 第一种:short-circuited短路
- 思考:若fallback方法内执行时抛出异常了呢?
- 声明
前言
上篇文章详细介绍了Hystrix的fallback降级逻辑,并且深入分析了其源码实现:getFallbackOrThrowException()
方法,但我在文末留下了一个小问题:Hystrix中哪些情况会触发它的降级逻辑呢?
带着这个疑问开始这篇文章的内容,本文将详细为你介绍触发Hystrix执行fallback逻辑的5种case,并且分别给出示例代码,让你既能学到,又能用到。
正文
Hystrix把它处理fallabck的全部逻辑都封装在了getFallbackOrThrowException()
方法里,从源码处来看只需知道有哪些地方调用了此方法便可得出答案。
触发fallback的5种情况
首先,哪些情况下可以触发Hystrix的降级逻辑这本身就是一个好问题。那为何是5种情况呢?其实这个答案从官方的Hystrix原理图中能看到触发fallback回退的地方一共有5处:图中共色字体已经标出。
其实,站在源码的角度看,此问题亦可转换一下,也可这么问:调用getFallbackOrThrowException()
的地方有几处呢?如下截图也展示了,恰好也是5处:
针对这5种case,下面一一作出解释并且给出针对性的触发示例。
第一种:short-circuited短路
- 触发条件:断路器
HystrixCircuitBreaker
已处于打开状态。请求再次进来便直接执行短路逻辑 - 异常类型:
new RuntimeException("Hystrix circuit short-circuited and is OPEN")
- 对应方法名:
handleShortCircuitViaFallback()
AbstractCommand:
// 可以看到异常是它内部new出来的,然后调用
private Observable<R> handleShortCircuitViaFallback() {
Exception shortCircuitException = new RuntimeException("Hystrix circuit short-circuited and is OPEN");
// 设置结果:异常类型为RuntimeException类型
executionResult = executionResult.setExecutionException(shortCircuitException);
// 异常消息关键字:short-circuited
return getFallbackOrThrowException(this, HystrixEventType.SHORT_CIRCUITED, FailureType.SHORTCIRCUIT, "short-circuited", shortCircuitException);
}
示例
首先自定义一个Command:
代码语言:javascript复制private static class FallabckDemo extends HystrixCommand<String> {
private final String name;
public FallabckDemo(String name) {
super(HystrixCommandGroupKey.Factory.asKey("fallbackDemoGroup"));
this.name = name;
}
@Override
protected String run() {
System.out.printf("健康信息:%s,断路器是否打开:%sn", getMetrics().getHealthCounts(), circuitBreaker.isOpen());
if (name == null) {
throw new NullPointerException();
}
return "Hello " name "!";
}
@Override
protected String getFallback() {
Throwable e = getExecutionException(); // 导致目标方法执行失败的异常类型
if (!(e instanceof NullPointerException)) {
System.out.printf("异常类型:%s,信息:%sn", e.getClass().getSimpleName(), e.getMessage());
}
return "this is fallback msg";
}
}
可以看到run方法中,若name为null就抛出NPE异常。下面模拟请求来触发熔断器:
代码语言:javascript复制@Test
public void fun1() throws InterruptedException {
// 10秒钟大于20个请求 失败数超过50%就触发熔断
// 35个请求,还可以看到半开状态哦~~
for (int i = 0; i < 35; i ) {
String name = i % 2 == 0 ? null : "demo"; // 用于模拟50%的错误率
FallabckDemo demo = new FallabckDemo(name);
demo.execute(); //同步执行
// 因为10秒内要至少放20个请求进去
// 因为第一个请求先发出再休眠,所以此处取值500ms是没有问题的
TimeUnit.MILLISECONDS.sleep(500);
}
}
这里500毫秒发一个请求,可以有很好的效果能看到熔断器打开、半开等状态,运行程序,控制台输出:
代码语言:javascript复制// 说明:因为输出这句话时run方法还没执行完,所以这里是0。第一个请求其实是失败哦所有抛出异常信息
健康信息:HealthCounts[0 / 0 : 0%],断路器是否打开:false
java.lang.NullPointerException: null
at com.yourbatman.hystrix.TestFallback$FallabckDemo.run(TestFallback.java:41)
at com.yourbatman.hystrix.TestFallback$FallabckDemo.run(TestFallback.java:28)
...
// 因为这次请求是正常的,所以后面就不会根有异常栈了。间隔一次错误一次,交替进行,保证错误率50%以上
健康信息:HealthCounts[1 / 1 : 100%],断路器是否打开:false
健康信息:HealthCounts[1 / 2 : 50%],断路器是否打开:false
java.lang.NullPointerException: null
at com.yourbatman.hystrix.TestFallback$FallabckDemo.run(TestFallback.java:41)
at com.yourbatman.hystrix.TestFallback$FallabckDemo.run(TestFallback.java:28)
...
...
...
健康信息:HealthCounts[9 / 17 : 52%],断路器是否打开:false
健康信息:HealthCounts[9 / 18 : 50%],断路器是否打开:false
java.lang.NullPointerException: null
at com.yourbatman.hystrix.TestFallback$FallabckDemo.run(TestFallback.java:41)
at com.yourbatman.hystrix.TestFallback$FallabckDemo.run(TestFallback.java:28)
...
健康信息:HealthCounts[10 / 19 : 52%],断路器是否打开:false
// 从这开始:断路器就开启了,所以如果请求再次进来,直接抛出异常short-circuited从而进入fallabck的逻辑
异常类型:RuntimeException,信息:Hystrix circuit short-circuited and is OPEN
异常类型:RuntimeException,信息:Hystrix circuit short-circuited and is OPEN
...
异常类型:RuntimeException,信息:Hystrix circuit short-circuited and is OPEN
// 事件过了5秒后,进入半开状态:尝试放一个请求进来。只可惜又报错了,断路器继续保持开启状态
健康信息:HealthCounts[5 / 10 : 50%],断路器是否打开:true
java.lang.NullPointerException: null
at com.yourbatman.hystrix.TestFallback$FallabckDemo.run(TestFallback.java:41)
at com.yourbatman.hystrix.TestFallback$FallabckDemo.run(TestFallback.java:28)
...
异常类型:RuntimeException,信息:Hystrix circuit short-circuited and is OPEN
异常类型:RuntimeException,信息:Hystrix circuit short-circuited and is OPEN
...
这是第一个例子,所以写得比较全面,希望读者可以认真读懂该例子,结合前面所学的熔断器原理加以理解,这才能叫真正掌握了Hystrix的熔断器原理。
顺带说明一句:为何这里的NPE异常会打印到控制台,是因为
handleFailureViaFallback
方法处有一句logger.debug("Error executing HystrixCommand.run(). Proceeding to fallback logic ...", underlying);
,而我的日志级别是debug,所以控制台里都会打印run里面的异常信息~另外,下面的异常类型因为不是run方法里面的,所以默认是不会打印输出的哦
第二种:threadpool-rejected线程池拒绝
- 触发条件:当线程池满了,再有请求进来时触发此拒绝逻辑
- 异常类型:
new RejectedExecutionException("Rejected command because thread-pool queueSize is at rejection threshold.")
- 该异常由
HystrixContextScheduler
里去申请线程池资源时抛出
- 该异常由
- 对应方法名:
handleThreadPoolRejectionViaFallback(Exception underlying)
- 因异常由方法“外部”抛出,所以此方法有入参
AbstractCommand:
// 标记threadPool#markThreadRejection
// 这个会统计到HystrixThreadPoolMetrics指标信息里去
private Observable<R> handleThreadPoolRejectionViaFallback(Exception underlying) {
...
threadPool.markThreadRejection();
// 异常信息:could not be queued for execution
return getFallbackOrThrowException(this, HystrixEventType.THREAD_POOL_REJECTED, FailureType.REJECTED_THREAD_EXECUTION, "could not be queued for execution", underlying);
}
示例
针对上例做出些许改变:
代码语言:javascript复制1、for循环放任务时,改成queue()异步的:`demo.queue()`
说明:
queue()
方法调用后,run方法/fallback方法也都是立马会执行的哦,只是它们是异步去执行,不会阻塞主线程而已
这样子的话,线程池就会被立即打满(比较默认只有10个)。
运行测试程序,可以看到控制台就一直抛出RejectedExecutionException
异常:
健康信息:HealthCounts[0 / 0 : 0%],断路器是否打开:false
异常类型:RejectedExecutionException,信息:Task java.util.concurrent.FutureTask@52aa2946 rejected from java.util.concurrent.ThreadPoolExecutor@4de5031f[Running, pool size = 10, active threads = 10, queued tasks = 0, completed tasks = 0]
...
第三种:semaphore-rejected信号量拒绝
- 触发条件:当信号量木有资源了,再有请求进来时触发信号量拒绝逻辑。
- 异常类型:
new RuntimeException("could not acquire a semaphore for execution")
- 请注意哦,它是
RuntimeException
,和上有不同
- 请注意哦,它是
- 对应方法名:
handleSemaphoreRejectionViaFallback()
- 它是内部自己new的异常,所以木有入参
AbstractCommand:
private Observable<R> handleSemaphoreRejectionViaFallback() {
Exception semaphoreRejectionException = new RuntimeException("could not acquire a semaphore for execution");
executionResult = executionResult.setExecutionException(semaphoreRejectionException);
// 异常关键字:could not acquire a semaphore for execution
return getFallbackOrThrowException(this, HystrixEventType.SEMAPHORE_REJECTED, FailureType.REJECTED_SEMAPHORE_EXECUTION,
"could not acquire a semaphore for execution", semaphoreRejectionException);
}
示例
类同于线程池方式,略。
第四种:timed-out超时
- 触发条件:当目标方法执行超时,会触发超时的回退逻辑。
- 异常类型:
new HystrixTimeoutException()
。最终异常类型为:new TimeoutException()
- Hystrix的超时是使用
TimerListener
来控制实现的。默认超时机制是开启的,时间是1s
- Hystrix的超时是使用
- 对应方法名:
handleTimeoutViaFallback()
AbstractCommand:
// 异常信息关键字:timed-out
// 注意异常类型是new出来的:new TimeoutException()。无任何msg消息哦~
private Observable<R> handleTimeoutViaFallback() {
return getFallbackOrThrowException(this, HystrixEventType.TIMEOUT, FailureType.TIMEOUT, "timed-out", new TimeoutException());
}
示例
模拟这个case非常的简单,只需让run方法睡一会即可达到目的:TimeUnit.SECONDS.sleep(2);
。
然后书写测试用例:
代码语言:javascript复制@Test
public void fun3() {
FallabckDemo demo = new FallabckDemo("name");
String result = demo.execute();
System.out.println(result);
}
运行程序,输出:
代码语言:javascript复制异常类型:HystrixTimeoutException,信息:null
this is fallback msg
抛出超时异常HystrixTimeoutException
,正常fallback。
第五种:failed执行失败
- 触发条件:
command
执行失败,也就是你的run方法里执行失败(抛出了运行时异常)时,执行此部分逻辑 - 异常类型:run方法里的任意运行时异常类型,比如NPE异常
- 对应方法名:
handleFailureViaFallback()
AbstractCommand:
// 只要是用户自己的代码问题,产生的异常,均到交到此处处理
private Observable<R> handleFailureViaFallback(Exception underlying) {
// 把用户产生的异常输出。debug级别哦~
logger.debug("Error executing HystrixCommand.run(). Proceeding to fallback logic ...", underlying);
eventNotifier.markEvent(HystrixEventType.FAILURE, commandKey);
// 记录异常类型到结果
executionResult = executionResult.setException(underlying);
return getFallbackOrThrowException(this, HystrixEventType.FAILURE, FailureType.COMMAND_EXCEPTION, "failed", underlying);
}
该方法你可以认为是个兜底实现,除了上面4种之外,但凡有其它任何异常均会交给它来处理(当然喽,HystrixBadRequestException
类型除外);
示例
第一个示例演示的就是NPE异常,它最终便会经过handleFailureViaFallback()
处理,具体示例代码本处略,建议初学者有兴趣可自行动手书写。
思考:若fallback方法内执行时抛出异常了呢?
首先,官方建议fallabck里返回的是常量/缓存里的值(比如Map里的值),所以fallback里出现异常的理应几乎为0。但建议总归是建议,若你真要在里面写复杂逻辑:比如通过RPC去获取数据,那错误率就高了。那么问题来了:万一出现此情况,是何表现呢???
这道题留给读者自行思考,建议自己动手写个示例来证明你的猜想,比较say say easy,do do hard。至于其原理,请参考前一篇内容的讲解,那里有你想要的原理解释。
总结
关于Hystrix触发fallback降级逻辑的5种情况就介绍到这了。本文内容还是比较全面的,针对于各种情况都给出了对应的触发示例代码,相信这样对你理解起来更加的无障碍些。
针对学习提个小小建议:建议多动手实践才能把知识真正掌握,毕竟很多知识都是一听就会,一做就错的。