前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[享学Netflix] 三十二、Hystrix抛出HystrixBadRequestException异常为何不熔断?

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

作者头像
YourBatman
发布2020-03-18 19:41:18
3.9K2
发布2020-03-18 19:41:18
举报
文章被收录于专栏:BAT的乌托邦BAT的乌托邦

我学习,我骄傲,我为国家省口罩。 代码下载地址: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是无效的,所以它并不会触发熔断器。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目录
  • 前言
  • 正文
    • 认识HystrixBadRequestException
      • 熔断器的数据从哪儿收集?
        • 触发fallback的情况和熔断器事件类型的对应关系
      • 为何不会触发熔断器?
        • handleFallback
      • 使用场景
      • 总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档