Hystrix是一个限流、降级容错框架,它能很好的保护我们的接口、应用。这很大程度上得益于它提供了fallback机制:回退(也叫降级)。本文主要讲述了Hystrix的fallback的主要特性和核心点:

  • Hystrix的fallback回退/降级逻辑
  • Hystrix触发fallback降级逻辑的5种情况
  • Hystrix抛出HystrixBadRequestException异常不熔断
1、Hystrix的fallback回退/降级逻辑

xxxCommand系列正常的执行逻辑、以及出现异常的回退逻辑均在HystrixCommandHystrixObservableCommand二者的抽象父类AbstractCommand里实现的。而对fallback的所有执行逻辑便在方法getFallbackOrThrowException()。

1.1、getFallbackOrThrowException() 源码解读:

该方法是AbstractCommand的一个私有方法,语义是:执行fallback回滚或者抛出异常(一般为HystrixRuntimeException异常类型,当然不是绝对的)。

值得注意的是:若执行到了这一步,Hystrix它能100%保证无论如何都能尽快把你的请求掐断,避免系统被拖死。虽然Hystrix它建议你不要在getFallback()里面写耗时的阻塞的(比如请求网络)逻辑,但是建议归建议,但还是可能有人不这么做怎么办呢?所以fallbackSemaphoreOverride信号量的作用:专门用于fallback的信号量控制,决不让其堆积请求。

需要注意的是:调用此方法必传一个Exception,因为Hystrix认为执行进入到这里肯定是发生了一个异常才行的。

AbstractCommand:

	// _cmd:命令对象
	// eventType:事件类型
	// failureType:失败类型枚举(BAD_REQUEST_EXCEPTION...) 见下面枚举定义
	// message:失败的消息。如timed-out、failed、short-circuited等
	// Exception:导致失败的异常(一定只有异常才能导致失败),如java.util.concurrent.TimeoutException
	private Observable<R> getFallbackOrThrowException(
		AbstractCommand<R> _cmd, HystrixEventType eventType, 
		FailureType failureType, String message, 
		Exception originalException) {
	
		// 因为fallback也是在线程池里面执行,所以也是需要传递数据的
		HystrixRequestContext requestContext = HystrixRequestContext.getContextForCurrentThread();
		// 统计相关事件的次数:详见事件计数器
		executionResult = executionResult.addEvent((int) latency, eventType);
		...
		// 如果异常类型是ExceptionNotWrappedByHystrix类型
		if (shouldNotBeWrapped(originalException)){
			... // 触发executionHook.onError()动作
			return Observable.error(e);
		// 是否是不可恢复的异常类型:比如StackOverflowError/VirtualMachineError...
		// 因为并不是所有的Error都不会恢复,所以这里例举出来。如果是不可恢复的错误,就包装一下抛出
		} else if (isUnrecoverable(originalException)) {
			...
			// 若是不可恢复错误,转未HystrixRuntimeException异常抛出
			return Observable.error(new HystrixRuntimeException(failureType, ... );
		} else {
			// 若是可自己恢复的Error,如IOError,那就输入一句日志即可
			if (isRecoverableError(originalException)) { ... }

			// =======普通异常类型(比如NPE之类的),那就开始执行fallback函数========
			if (properties.fallbackEnabled().get()) { // 显然默认是开启的,不建议关闭它
				// 若你没指定,默认使用的是TryableSemaphoreActual 10个信号量
				// 你可以通过fallbackIsolationSemaphoreMaxConcurrentRequests指定这个值
				// 该信号量用于杜绝你的fallback还非常耗时的case,以防万一
				TryableSemaphore fallbackSemaphore = getFallbackSemaphore();

				...
				// 最终return的可观察对象
				// getFallbackObservable是个抽象方法,由子类提供
				// 比如command它就是使用Observable.just(getFallback())把任意对象转换为Observable
				// 当然它是个defer的Observable
				Observable<R> fallbackExecutionChain;
				// 若还有信号量资源就继续执行(否则会会做异常处理,见下面)
				if (fallbackSemaphore.tryAcquire()) { 
					// 用户是否自定义了fallback函数
					// 也就是是否重写了getFallback()方法嘛
					if (isFallbackUserDefined()) { 
						... // executionHook.onFallbackStart(this);
						fallbackExecutionChain = getFallbackObservable();
					} else { // 区别是:若没有复写这个方法,就不会触发onFallbackStart()动作
						fallbackExecutionChain = getFallbackObservable();
					}
					// 绑定固定的处理函数
					return fallbackExecutionChain
								// 作用于每个iter身上:setRequestContextIfNeeded(requestContext);
								// 确保上下文是同一个,数据是通的
			                    .doOnEach(setRequestContext)
			                    // 主要是为了触发executionHook相关回调
			                    .lift(new FallbackHookApplication(_cmd))
			                    .lift(new DeprecatedOnFallbackHookApplication(_cmd))
			                    // 发布事件:HystrixEventType.FALLBACK_EMIT
			                    // executionResult.addEvent(HystrixEventType.FALLBACK_EMIT)
			                    .doOnNext(markFallbackEmit)
			                    // 完成了。发布事件:HystrixEventType.FALLBACK_SUCCESS
			                    // 证明fallback成功
			                    .doOnCompleted(markFallbackCompleted)
			                    // fallback失败(出现异常了),发布事件
			                    // HystrixEventType.FALLBACK_MISSING、FALLBACK_FAILURE
			                    .onErrorResumeNext(handleFallbackError)
			                    // 释放信号量:fallbackSemaphore.release();
			                    .doOnTerminate(singleSemaphoreRelease)
			                    // fallbackSemaphore.release();
			                    .doOnUnsubscribe(singleSemaphoreRelease);
				} else { // 此else对应逻辑:信号量不够的情况
					return handleFallbackRejectionByEmittingError();
				}
			} else { // 此else对应逻辑:properties禁用了fallback的情况
				return handleFallbackDisabledByEmittingError(originalException, failureType, message);
			}
		}
	}


// 定义失败枚举:一个7种类型
HystrixRuntimeException:
    public static enum FailureType {
        BAD_REQUEST_EXCEPTION, COMMAND_EXCEPTION, TIMEOUT, SHORTCIRCUIT, REJECTED_THREAD_EXECUTION, REJECTED_SEMAPHORE_EXECUTION, REJECTED_SEMAPHORE_FALLBACK
    }
1.2、fallback函数执行成功or执行失败的处理

fallback函数执行成功or执行失败的处理
针对以上fallbackExecutionChain被观察对象的执行,还需注意关心它的执行成功or失败,需要做对应的处理:

  • doOnNext(markFallbackEmit):每发射一个数据(执行fallback方法之前),给事件计数executionResult.addEvent(HystrixEventType.FALLBACK_EMIT)
  • doOnCompleted(markFallbackCompleted):正常执行完成(fallback未抛出异常)时,记录结果事件executionResult.addEvent((int) latency, HystrixEventType.FALLBACK_SUCCESS)
  • onErrorResumeNext(handleFallbackError):若执行fallback时候发生错误(重点),就执行如下逻辑:
AbstractCommand:

// 若异常类型是该类型(比如你使用HystrixCommand,但没重写getFallback()方法,执行就抛出此异常)
// 就包装为HystrixRuntimeException给你抛出
// 并且有你熟悉的抛错消息: message + " and no fallback available."
if (fe instanceof UnsupportedOperationException) {
	...
	// 记录结果:事件类型是FALLBACK_MISSING
	executionResult.addEvent((int) latency, HystrixEventType.FALLBACK_MISSING);
	return Observable.error(new HystrixRuntimeException(failureType, ...);
} else { // 你的fallback方法里抛出了其它异常
	...
	// 记录事件,此时事件类型是fallback执行失败FALLBACK_FAILURE
	executionResult.addEvent((int) latency, HystrixEventType.FALLBACK_FAILURE);
	return Observable.error(new HystrixRuntimeException(failureType ...  message + " and fallback failed." ...);
}

对于fallback执行失败的case,请注意错误消息and no fallback available.和and fallback failed.的区别。前者是木有提供fallback函数,后者是提供了但是执行时抛错了;

另外,源码处有两个else逻辑此处也给个简要描述:

  • 如果properties.fallbackEnabled() = false显示禁用了fallback功能,最终会触发handleFallbackDisabledByEmittingError()方法(此为唯一调用处):
AbstractCommand:

	private Observable<R> handleFallbackDisabledByEmittingError(Exception underlying, FailureType failureType, String message) {
		... // 触发executionHook.onError动作
		// 转换为一个HystrixRuntimeException抛出
		return Observable.error(new HystrixRuntimeException(failureType, ... " and fallback disabled." ... );
	}
  • 如果执行fallabck时请求信号量资源不够用了,那么执行handleFallbackRejectionByEmittingError()方法(此处为唯一调用处):
AbstractCommand:

	private Observable<R> handleFallbackRejectionByEmittingError() {
		...
		// 这里记录了结果哦。统计对应事件的次数(毕竟出现此case的数据还是蛮有意义的)
		executionResult = executionResult.addEvent((int) latencyWithFallback, HystrixEventType.FALLBACK_REJECTION);
		return Observable.error(new HystrixRuntimeException(FailureType.REJECTED_SEMAPHORE_FALLBACK,... " fallback execution rejected." ...);
    }
1.3、getFallbackOrThrowException回退步骤文字总结

首先需要明确:执行此fallabck步骤肯定是发生了Exception异常的(当然有可能是Error错误),所以异常类型很关键,此处用 e 来表示源生异常类型(如目标方法自己产生的NPE)。

  • 若 e 不需要被包装,那就不用使用HystrixRuntimeException去包它了,直接返回:Observable.error(e);
    ExceptionNotWrappedByHystrix是个标记接口:若你的异常类型实现了此接口,那么抛出此类型的异常将不会再被 HystrixRuntimeException包起来了
  • 若e是不可恢复的异常类型如:StackOverflowError/VirtualMachineError/ThreadDeath/LinkageError,那就直接包装为HystrixRuntimeException类型抛出。
  • 到这一步,e的类型“过关”了,着手执行fallabck逻辑。若禁用了fallabck,就执行handleFallbackDisabledByEmittingError()方法 -> 抛出HystrixRuntimeException异常,否则继续。
  • 准备执行fallabck函数,先请求fallabck专用的信号量。若无资源了,那就执行handleFallbackRejectionByEmittingError()犯法 -> 抛出HystrixRuntimeException异常,否则继续。否则继续
  • 通过抽象方法getFallbackObservable()拿到被观察对象Observable,然后便可开始执行目标fallabck函数了。其中执行目标fallback函数时分为成功or失败。
    成功: 执行doOnCompleted放,整成记录FALLBACK_SUCCESS事件到结果即可
    失败:分为未提供fallabck函数和fallback函数内部抛出了异常两种case -> 均抛出HystrixRuntimeException异常,对应异常消息是著名的:and no fallback available.和and fallback failed.。
2、Hystrix触发fallback降级逻辑的5种情况

Hystrix把它处理fallabck的全部逻辑都封装在了getFallbackOrThrowException()方法里,从源码处来看只需知道有哪些地方调用了此方法便可得出答案。

从官方的Hystrix原理图中能看到触发fallback回退的地方一共有5处:图中共色字体已经标出:
在这里插入图片描述
站在源码的角度看,此问题亦可转换一下,也可这么问:调用getFallbackOrThrowException()的地方有几处呢?如下截图也展示了,恰好也是5处:
在这里插入图片描述
针对这5种case,下面一一作出解释并且给出针对性的触发示例;

2.1、short-circuited短路
  • 触发条件:断路器HystrixCircuitBreaker已处于打开状态。请求再次进来便直接执行短路逻辑
  • 异常类型:new RuntimeException(“Hystrix circuit short-circuited and is OPEN”)
  • 对应方法名:handleShortCircuitViaFallback()
AbstractCommand:

   	// 可以看到异常是它内部new出来的,然后调用
    private Observable<R> handleShortCircuitViaFallback() {
        Exception shortCircuitException = new RuntimeException("Hystrix circuit short-circuited and is OPEN");
        // 设置结果:异常类型为RuntimeException类型
        executionResult = executionResult.setExecutionException(shortCircuitException);
        // 异常消息关键字:short-circuited
        return getFallbackOrThrowException(this, HystrixEventType.SHORT_CIRCUITED, FailureType.SHORTCIRCUIT, "short-circuited", shortCircuitException);
    }
2.2、threadpool-rejected线程池拒绝
  • 触发条件:当线程池满了,再有请求进来时触发此拒绝逻辑
  • 异常类型:new RejectedExecutionException(“Rejected command because thread-pool queueSize is at rejection threshold.”)
    该异常由HystrixContextScheduler里去申请线程池资源时抛出
  • 对应方法名:handleThreadPoolRejectionViaFallback(Exception underlying)
    因异常由方法“外部”抛出,所以此方法有入参
AbstractCommand:

// 标记threadPool#markThreadRejection
// 这个会统计到HystrixThreadPoolMetrics指标信息里去
  private Observable<R> handleThreadPoolRejectionViaFallback(Exception underlying) {
  	...
      threadPool.markThreadRejection();
      // 异常信息:could not be queued for execution
      return getFallbackOrThrowException(this, HystrixEventType.THREAD_POOL_REJECTED, FailureType.REJECTED_THREAD_EXECUTION, "could not be queued for execution", underlying);
  }
2.3、semaphore-rejected信号量拒绝
  • 触发条件:当信号量木有资源了,再有请求进来时触发信号量拒绝逻辑。
  • 异常类型:new RuntimeException(“could not acquire a semaphore for execution”)
    请注意它是RuntimeException,和上有不同
  • 对应方法名:handleSemaphoreRejectionViaFallback()
    它是内部自己new的异常,所以木有入参
AbstractCommand:

private Observable<R> handleSemaphoreRejectionViaFallback() {
    Exception semaphoreRejectionException = new RuntimeException("could not acquire a semaphore for execution");
    executionResult = executionResult.setExecutionException(semaphoreRejectionException);
	// 异常关键字:could not acquire a semaphore for execution
    return getFallbackOrThrowException(this, HystrixEventType.SEMAPHORE_REJECTED, FailureType.REJECTED_SEMAPHORE_EXECUTION,
            "could not acquire a semaphore for execution", semaphoreRejectionException);
2.4、timed-out超时
  • 触发条件:当目标方法执行超时,会触发超时的回退逻辑。
  • 异常类型:new HystrixTimeoutException()。最终异常类型为:new TimeoutException()
    Hystrix的超时是使用TimerListener来控制实现的。默认超时机制是开启的,时间是1s
  • 对应方法名:handleTimeoutViaFallback()
AbstractCommand:

// 异常信息关键字:timed-out
// 注意异常类型是new出来的:new TimeoutException()。无任何msg消息哦~
private Observable<R> handleTimeoutViaFallback() {
    return getFallbackOrThrowException(this, HystrixEventType.TIMEOUT, FailureType.TIMEOUT, "timed-out", new TimeoutException());
}
2.5、failed执行失败
  • 触发条件:command执行失败,也就是你的run方法里执行失败(抛出了运行时异常)时,执行此部分逻辑
  • 异常类型:run方法里的任意运行时异常类型,比如NPE异常
  • 对应方法名:handleFailureViaFallback()
AbstractCommand:

// 只要是用户自己的代码问题,产生的异常,均到交到此处处理
private Observable<R> handleFailureViaFallback(Exception underlying) {
	// 把用户产生的异常输出。debug级别哦~
	logger.debug("Error executing HystrixCommand.run(). Proceeding to fallback logic ...", underlying);	
	eventNotifier.markEvent(HystrixEventType.FAILURE, commandKey);
	// 记录异常类型到结果
	executionResult = executionResult.setException(underlying);
	return getFallbackOrThrowException(this, HystrixEventType.FAILURE, FailureType.COMMAND_EXCEPTION, "failed", underlying);
}

该方法你可以认为是个兜底实现,除了上面4种之外,但凡有其它任何异常均会交给它来处理(当然HystrixBadRequestException类型除外);

思考:若fallback方法内执行时抛出异常了呢?
首先,官方建议fallabck里返回的是常量/缓存里的值(比如Map里的值),所以fallback里出现异常的理应几乎为0。但建议总归是建议,若你真要在里面写复杂逻辑:比如通过RPC去获取数据,那错误率就高了。那么问题来了:万一出现此情况,是何表现呢???

3、Hystrix抛出HystrixBadRequestException异常不熔断

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

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

通过前面分析我们已经了解到了Hystrix触发fallback降级逻辑的5种情况,也就是:
1. short-circuited短路
2. threadpool-rejected线程池拒绝
3. semaphore-rejected信号量拒绝
4. timed-out超时
5. failed执行失败

出现这些类型的失败均会触发Hystrix的fallback机制。但HystrixBadRequestException这类型的异常将不会触发fallabck机制。下面我们将介绍这个场景;

3.1、认识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异常而意外熔断的问题。

3.2、熔断器的数据从哪儿收集?

我们知道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的情况和熔断器事件类型的对应关系
下面绘制一张表格表达其对应关系:
在这里插入图片描述
对此表格做如下几点说明:

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

结合熔断器统计数据类HealthCounts关心的几个事件类型来说:除了HystrixBadRequestException异常导致的失败,其它均会被收集作为断路器的指标数据。
short-circuited短路这种case就不用收集啦,因为都已经短路了,就没必要再收集了,否则断路器永远都自愈不回来就尴尬了;

3.3、为何不会触发熔断器?

所有的命令执行,最终在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);
        }
    }
};

下面具体看看handleBadRequestByEmittingError()对该异常HystrixBadRequestException的处理;

handleBadRequestByEmittingError():BadRequestException异常类型:

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作为健康指标所统计的,因此它并不会触发熔断器。

3.4、使用场景

了解了HystrixBadRequestException的这个特性后,使用场景可根据具体业务而定喽。比如我们最为常用的场景便是在Feign上自定义一个错误解码器ErrorDecoder,然后针对于错误码是400的响应统一转换为HystrixBadRequestException异常抛出,这样是比较优雅的一种实践方案。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐