我学习,我骄傲,我为国家省口罩。 代码下载地址:https://github.com/f641385712/netflix-learning
目录- 前言
- 正文
- 认识HystrixBadRequestException
- 熔断器的数据从哪儿收集?
- 触发fallback的情况和熔断器事件类型的对应关系
- 为何不会触发熔断器?
- handleFallback
- handleBadRequestByEmittingError()
- 使用场景
- 总结
- 声明
- 认识HystrixBadRequestException
- 熔断器的数据从哪儿收集?
- 触发fallback的情况和熔断器事件类型的对应关系
- 为何不会触发熔断器?
- handleFallback
- handleBadRequestByEmittingError()
- handleFallback
- 使用场景
- 声明
前言
通过前面文章我们知道了,Hystrix是个强大的熔断降级框架:收集目标方法的成功、失败等指标信息,触发熔断器。其中失败信息通过异常来表示,交给Hystrix进行统计。
但是,有的时候有些异常是并不能触发熔断的,比如请求参数异常等,那怎么办呢?或许你已经知道了结论:目标方法执行抛出异常时,除HystrixBadRequestException
之外,其他异常都会认为是Hystrix命令执行失败并触发服务降级处理逻辑。那么本文将深入研究为何如此,以及给出实践方案。
说明:阅读本文之前建议你已经了解了Hystrix的回退机制,如上篇文章:三十一、Hystrix触发fallback降级逻辑的5种情况及代码示例
正文
通过上篇文章,我们已经了解到了Hystrix触发fallback降级逻辑的5种情况,也就是:
- short-circuited短路
- threadpool-rejected线程池拒绝
- semaphore-rejected信号量拒绝
- timed-out超时
- failed执行失败
出现这些类型的失败均会触发Hystrix的fallback机制。本文将介绍HystrixBadRequestException
这类型的异常将不会触发fallabck机制。
认识HystrixBadRequestException
这个类本身并没有什么好说的,就是一个非常简单的运行时异常:
代码语言:javascript复制public class HystrixBadRequestException extends RuntimeException {
private static final long serialVersionUID = -8341452103561805856L;
public HystrixBadRequestException(String message) {
super(message);
}
public HystrixBadRequestException(String message, Throwable cause) {
super(message, cause);
}
}
不过它的Javadoc对该类的作用描述:用提供的参数或状态表示错误而不是执行失败的异常。与HystrixCommand
抛出的所有其他异常不同,这不会触发回退,不会计算故障指标,因此不会触发断路器。
注意:当一个错误是由于用户输入IllegalArgumentException
引起时(比如手误),这个只应该使用,否则就会破坏容错和回退行为的目的。总的来说千万别盲目使用,使用得最多的case是:结合Feign错误编码器一起解决客户端400异常而意外熔断的问题~
熔断器的数据从哪儿收集?
在解释为何不会触发熔断器之前,首先需要明白熔断器的数据是从哪儿收集的?数据发射的源头是哪儿?
在详解HystrixCircuitBreaker
这篇文章的时候,我们知道它的健康指标数据来源于HealthCountsStream
这个数据流:统计时间窗口里面各桶的值,汇总为HealthCounts
对象输出。
可以简要复习下HealthCounts
这个类,它记录着滑动窗口期间的请求数,包括:总数、失败数、失败百分比。它会统计如下事件:
HystrixEventType.SUCCESS
HystrixEventType.FAILURE
HystrixEventType.TIMEOUT
HystrixEventType.THREAD_POOL_REJECTED
HystrixEventType.SEMAPHORE_REJECTED
这些事件中除了1
,其它均为失败。另外2-4不就正好对应着文首写着的触发fallback的前四种情况吗?
触发fallback的情况和熔断器事件类型的对应关系
下面绘制一张表格表达其对应关系:
失败情况 | 原始异常类型 | 是否触发fallback | 是否纳入熔断器统计 | 事件类型 |
---|---|---|---|---|
short-circuited短路 | RuntimeException | 是 | 是 | SHORT_CIRCUITED |
threadpool-rejected线程池拒绝 | RejectedExecutionException | 是 | 是 | THREAD_POOL_REJECTED |
semaphore-rejected信号量拒绝 | RuntimeException | 是 | 是 | SEMAPHORE_REJECTED |
timed-out超时 | TimeoutException | 是 | 是 | TIMEOUT |
failed失败 | 目标方法抛出的异常类型 | 是 | 是 | FAILURE |
HystrixBadRequestException | 该异常亦由目标方法抛出 | 否 | 否 | 无 |
对此表格做如下几点说明:
- 事件类型均为
HystrixEventType
类型,本处前缀省略 - 这里指的是原始异常类型,因为最终经过fallback处理后都会被包为
HystrixRuntimeException
- 实际上失败情况
HystrixBadRequestException
和failed失败
同属目标方法抛出的异常,只是前者比较特殊而已~
结合熔断器统计数据类HealthCounts
关心的几个事件类型来说:除了HystrixBadRequestException
异常导致的失败,其它均会被收集作为断路器的指标数据。
说明:
short-circuited短路
这种case就不用收集啦,因为都已经短路了,就没必要再收集了,否则断路器永远都自愈不回来就尴尬了
为何不会触发熔断器?
所有的命令执行,最终在executeCommandAndObserve()
方法内:
AbstractCommand:
private Observable<R> executeCommandAndObserve(final AbstractCommand<R> _cmd) {
...
return execution.doOnNext(markEmits)
.doOnCompleted(markOnCompleted)
.onErrorResumeNext(handleFallback)
.doOnEach(setRequestContext);
}
其它部分本文不用关心,仅需关心onErrorResumeNext(handleFallback)
这个函数,它的触发条件是:发射数据时(目标方法执行时)出现异常便会回调此函数,因此需要看看handleFallback
的逻辑。
说明:正常执行(成功)时不会回调此函数,而是回调的
doOnCompleted(markOnCompleted)
哦~
handleFallback
顾名思义,它是用于处理fallback的函数。
代码语言:javascript复制Func1<Throwable, Observable<R>> handleFallback = new Func1<Throwable, Observable<R>>() {
// 当大声异常时,回调此方法,该异常就是t
@Override
public Observable<R> call(Throwable t) {
// 若t就是Exception类型,那么t和e一样
// 若不是Exception类型,比如是Error类型。那就用Exception把它包起来
// new Exception("Throwable caught while executing.", t);
Exception e = getExceptionFromThrowable(t);
// 把异常写进结果里
executionResult = executionResult.setExecutionException(e);
// ==============针对不同异常的处理==============
if (e instanceof RejectedExecutionException) {
return handleThreadPoolRejectionViaFallback(e);
} else if (t instanceof HystrixTimeoutException) {
return handleTimeoutViaFallback();
} else if (t instanceof HystrixBadRequestException) {
return handleBadRequestByEmittingError(e);
} else {
return handleFailureViaFallback(e);
}
}
};
以上源码,针对不同异常类型的处理方法,除了针对HystrixBadRequestException
异常类型没讲述过,其它均在上篇文章有过详细阐述。下面具体看看handleBadRequestByEmittingError()
对该异常的处理。
handleBadRequestByEmittingError()
此方法专门用于处理HystrixBadRequestException
异常类型。
AbstractCommand:
private Observable<R> handleBadRequestByEmittingError(Exception underlying) {
Exception toEmit = underlying;
try {
long executionLatency = System.currentTimeMillis() - executionResult.getStartTimestamp();
// 请注意:这里发送的是BAD_REQUEST事件哦~~~~
eventNotifier.markEvent(HystrixEventType.BAD_REQUEST, commandKey);
executionResult = executionResult.addEvent((int) executionLatency, HystrixEventType.BAD_REQUEST);
// 留个钩子:调用者可以对异常类型进行偷天换日
Exception decorated = executionHook.onError(this, FailureType.BAD_REQUEST_EXCEPTION, underlying);
// 如果调用者通过hook处理完后还是HystrixBadRequestException类型,那就直接把数据发射出去
// 若不是,那就不管,还是发射原来的异常类型
if (decorated instanceof HystrixBadRequestException) {
toEmit = decorated;
} else {
logger.warn("ExecutionHook.onError returned an exception that was not an instance of HystrixBadRequestException so will be ignored.", decorated);
}
} catch (Exception hookEx) {
logger.warn("Error calling HystrixCommandExecutionHook.onError", hookEx);
}
return Observable.error(toEmit);
}
这就是HystrixBadRequestException
的特殊对待逻辑,它发出的事件类型是HystrixEventType.BAD_REQUEST
,而此事件类型是不会被HealthCounts
作为健康指标所统计的,因此它并不会触发熔断器。
使用场景
了解了HystrixBadRequestException
的这个特性后,使用场景可根据具体业务而定喽。比如我们最为常用的场景便是在Feign上自定义一个错误解码器ErrorDecoder
,然后针对于错误码是400的响应统一转换为HystrixBadRequestException
异常抛出,这样是比较优雅的一种实践方案。
总结
Hystrix抛出HystrixBadRequestException异常为何不会触发熔断?这个话题就先聊到这了,到此篇为止讲述完了Hystrix执行时所有的异常状态的处理方式。
小总结一下:Hystrix对异常HystrixBadRequestException
的处理发送的事件类型HystrixEventType.BAD_REQUEST
,而该事件类型对负责给熔断器收集指标数据的HealthCounts
是无效的,所以它并不会触发熔断器。