我学习,我骄傲,我为国家省口罩。
–> 返回专栏总目录 <–
代码下载地址:https://github.com/f641385712/netflix-learning
目录
前言
通过前面文章我们知道了,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
这个类本身并没有什么好说的,就是一个非常简单的运行时异常:
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的函数。
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
是无效的,所以它并不会触发熔断器。
声明
原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭
。你也可【左边扫码/或加wx:fsx641385712】邀请你加入我的 Java高工、架构师 系列群大家庭学习和交流。
- [享学Netflix] 一、Apache Commons Configuration:你身边的配置管理专家
- [享学Netflix] 二、Apache Commons Configuration事件监听机制及使用ReloadingStrategy实现热更新
- [享学Netflix] 三、Apache Commons Configuration2.x全新的事件-监听机制
- [享学Netflix] 四、Apache Commons Configuration2.x文件定位系统FileLocator和FileHandler
- [享学Netflix] 五、Apache Commons Configuration2.x别样的Builder模式:ConfigurationBuilder
- [享学Netflix] 六、Apache Commons Configuration2.x快速构建工具Parameters和Configurations
- [享学Netflix] 七、Apache Commons Configuration2.x如何实现文件热加载/热更新?
- [享学Netflix] 八、Apache Commons Configuration2.x相较于1.x使用上带来哪些差异?
- [享学Netflix] 九、Archaius配置管理库:初体验及基础API详解
- [享学Netflix] 十、Archaius对Commons Configuration核心API Configuration的扩展实现
- [享学Netflix] 十一、Archaius配置管理器ConfigurationManager和动态属性支持DynamicPropertySupport
- [享学Netflix] 十二、Archaius动态属性DynamicProperty原理详解(重要)
- [享学Netflix] 十三、Archaius属性抽象Property和PropertyWrapper详解
- [享学Netflix] 十四、Archaius如何对多环境、多区域、多云部署提供配置支持?
- [享学Netflix] 十五、Archaius和Spring Cloud的集成:spring-cloud-starter-netflix-archaius
- [享学Netflix] 十六、Hystrix断路器:初体验及RxJava简介
- [享学Netflix] 十七、Hystrix属性抽象以及和Archaius整合实现配置外部化、动态化
- [享学Netflix] 十八、Hystrix配置之:全局配置和实例配置
- [享学Netflix] 十九、Hystrix插件机制:SPI接口介绍和HystrixPlugins详解
- [享学Netflix] 二十、Hystrix跨线程传递数据解决方案:HystrixRequestContext
- [享学Netflix] 二十一、Hystrix指标数据收集(预热):滑动窗口算法(附代码示例)
- [享学Netflix] 二十二、Hystrix事件源与事件流:HystrixEvent和HystrixEventStream
- [享学Netflix] 二十三、Hystrix桶计数器:BucketedCounterStream
- [享学Netflix] 二十四、Hystrix在滑动窗口内统计:BucketedRollingCounterStream、HealthCountsStream
- [享学Netflix] 二十五、Hystrix累计统计流、分发流、最大并发流、配置流、功能流(附代码示例)
- [享学Netflix] 二十六、Hystrix指标数据收集器:HystrixMetrics(HystrixDashboard的数据来源)
- [享学Netflix] 二十七、Hystrix何为断路器的半开状态?HystrixCircuitBreaker详解
- [享学Netflix] 二十八、Hystrix事件计数器EventCounts和执行结果ExecutionResult
- [享学Netflix] 二十九、Hystrix执行过程核心接口:HystrixExecutable、HystrixObservable和HystrixInvokableInfo
- [享学Netflix] 三十、Hystrix的fallback回退/降级逻辑源码解读:getFallbackOrThrowException
- [享学Netflix] 三十一、Hystrix触发fallback降级逻辑的5种情况及代码示例