专栏首页BAT的乌托邦[享学Netflix] 三十二、Hystrix抛出HystrixBadRequestException异常为何不熔断?

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

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

目录

前言

通过前面文章我们知道了,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

这个类本身并没有什么好说的,就是一个非常简单的运行时异常:

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()方法内:

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是无效的,所以它并不会触发熔断器。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【小家java】Session和Cookie的区别和联系、分布式session的几种实现方式

    session和cookie两个概念,在web开发是经常被提及到的两个概念。它们之间有联系也有区别,那么本文主要解惑一些咱们平时挺关心的一些区别:

    YourBatman
  • [享学Netflix] 五十五、Ribbon负载均衡器执行上下文:LoadBalancerContext

    又是一个上下文概念。通过这么多篇的源码研究,发现Context上下文是常常遇到的一种“设计模式”,比如我们最为熟悉的ApplicationContext就是典型...

    YourBatman
  • 【小家Spring】Spring注解驱动开发---Spring Ioc容器中Bean的生命周期详解(BeanPostProcessor解析)

    我们可以自定义初始化和销毁方法;容器在bean进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法

    YourBatman
  • 【特写】陆奇与谷歌的最后一战,赌在人工智能,陆奇势在必赢

    【新智元导读】在微软时,陆奇给谷歌下了战书,输了;加入百度,战场也从搜索转到人工智能,陆奇说是“天时地利人和”,百度需要他100%的能力,他也势在必赢。 过去2...

    新智元
  • GXYCTF2019-PingPingPing

    看到flag.php 传?ip=127.0.0.1||cat%20flag.php

  • raft 系列解读(3) 之 代码实现最小规则followercandidateleader规则RequestVote RPCAppendEntries RPC

    首先,其实raft如果你不去看理论正确性的证明,光实现的话,只要按照raft里面给出的原则写代码就ok!如果代码写出来不正确,只能是你自己实现的问题。囧

    zhuanxu
  • 微型分布式架构设计范例

    设计该系统初衷是基于描绘业务(或机器集群)存储模型,分析代理缓存服务器磁盘存储与回源率的关系。

    mariolu
  • 如何设计一个麻雀般的微型分布式架构?

    设计该系统初衷是基于描绘业务(或机器集群)存储模型,分析代理缓存服务器磁盘存储与回源率的关系。系统意义是在腾讯云成本优化过程中,量化指导机房设备扩容。前半部分是...

    Java高级架构
  • 每天一道面试题 | day08

    session数据保存在服务器端,保存数据安全且存储数据量大,session是基于cookie进行信息处理的。

    剑走天涯
  • 28.go语言没有类 却可以在结构体或任意类型定义方法

    共2500字,阅读需6分钟 ? 在go语言中没有类。可是,是有方法的。 给结构体定义方法,在对应的 func 和方法名之间,加上方法的接收者就可以了。 比如,我...

    企鹅号小编

扫码关注云+社区

领取腾讯云代金券