[享学Netflix] 三十二、Hystrix抛出HystrixBadRequestException异常为何不熔断?

2020-03-18 19:41:18 浏览数 (1)

我学习,我骄傲,我为国家省口罩。 代码下载地址:https://github.com/f641385712/netflix-learning

目录
  • 前言
  • 正文
    • 认识HystrixBadRequestException
    • 熔断器的数据从哪儿收集?
      • 触发fallback的情况和熔断器事件类型的对应关系
    • 为何不会触发熔断器?
      • handleFallback
        • handleBadRequestByEmittingError()
    • 使用场景
  • 总结
    • 声明

前言

通过前面文章我们知道了,Hystrix是个强大的熔断降级框架:收集目标方法的成功、失败等指标信息,触发熔断器。其中失败信息通过异常来表示,交给Hystrix进行统计。

但是,有的时候有些异常是并不能触发熔断的,比如请求参数异常等,那怎么办呢?或许你已经知道了结论:目标方法执行抛出异常时,HystrixBadRequestException之外,其他异常都会认为是Hystrix命令执行失败并触发服务降级处理逻辑。那么本文将深入研究为何如此,以及给出实践方案。

说明:阅读本文之前建议你已经了解了Hystrix的回退机制,如上篇文章:三十一、Hystrix触发fallback降级逻辑的5种情况及代码示例


正文

通过上篇文章,我们已经了解到了Hystrix触发fallback降级逻辑的5种情况,也就是:

  1. short-circuited短路
  2. threadpool-rejected线程池拒绝
  3. semaphore-rejected信号量拒绝
  4. timed-out超时
  5. 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这个类,它记录着滑动窗口期间的请求数,包括:总数、失败数、失败百分比。它会统计如下事件:

  1. HystrixEventType.SUCCESS
  2. HystrixEventType.FAILURE
  3. HystrixEventType.TIMEOUT
  4. HystrixEventType.THREAD_POOL_REJECTED
  5. 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

该异常亦由目标方法抛出

对此表格做如下几点说明:

  1. 事件类型均为HystrixEventType类型,本处前缀省略
  2. 这里指的是原始异常类型,因为最终经过fallback处理后都会被包为HystrixRuntimeException
  3. 实际上失败情况HystrixBadRequestExceptionfailed失败同属目标方法抛出的异常,只是前者比较特殊而已~

结合熔断器统计数据类HealthCounts关心的几个事件类型来说:除了HystrixBadRequestException异常导致的失败,其它均会被收集作为断路器的指标数据。

说明:short-circuited短路这种case就不用收集啦,因为都已经短路了,就没必要再收集了,否则断路器永远都自愈不回来就尴尬了


为何不会触发熔断器?

所有的命令执行,最终在executeCommandAndObserve()方法内:

代码语言:javascript复制
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异常类型。

代码语言:javascript复制
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是无效的,所以它并不会触发熔断器。

0 人点赞