Hystrix 原理分析

降级有几种方案

降级分类

降级主要分手动降级和自动降级,手动降级其实就是在代码里加上一些开关把功能直接关闭,自动降级可以分为以下几种:

  1. 超时降级
    配置好超时时间和超时重试次数和机制,并使用异步机制探测恢复情况。
  2. 失败次数降级
    失败次数达到阈值自动降级,同样要使用异步机制探测回复情况。
  3. 故障降级
    如要调用的远程服务挂掉了(网络故障、DNS 故障、HTTP 服务返回错误的状态码和 RPC 服务抛出异常),则可以直接降级。
    和上面那种失败次数降级原理类似,只是需要区分错误类型。
  4. 限流降级
    单位时间内调用次数超过阈值,可以使用暂时屏蔽的方式来进行短暂的屏蔽。

降级处理方案

降级后,在代码层面,可以进行的处理策略如下:

  1. 抛异常
  2. 返回 NULL
  3. 调用 Mock 数据
  4. 调用 Fallback 处理逻辑

Hystrix 原理

Hystrix工作流程

4 种调用方法

  • toObservable()
    最基础的 API,直接返回 Observable,未作订阅,需要由用户来发起订阅;
  • observe()
    调用 #toObservable() 方法,并向 Observable 注册 rx.subjects.ReplaySubject 发起订阅,这会立刻触发 run()方法的执行。
  • queue()
    调用 #toObservable() 方法的基础上,调用:Observable#toBlocking() 和 BlockingObservable#toFuture() 返回 Future 对象,实现了异步调用
  • execute()
    调用 #queue() 方法的基础上,调用 Future#get() 方法,同步返回 #run() 的执行结果。

HystrixCommand.execute
-> HystrixCommand.queue
-> AbstractCommand.toObservable

AbstractCommand.observe
-> AbstractCommand.toObservable

隔离

限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用,包括线程池隔离信号量隔离
开启配置:
HystrixCommand(…, Setter.andCommandPropertiesDefaults(
HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)), …)
-> withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE) 配置隔离策略,线程池同理

实施信号量隔离:
AbstractCommand.toObservable
…中略
-> AbstractCommand.applyHystrixSemantics
-> TryableSemaphore.tryAcquire 如果没开启信号量模式,这个方法永远返回 true

实施线程隔离:
AbstractCommand.toObservable
…中略
-> AbstractCommand.applyHystrixSemantics
-> AbstractCommand.executeCommandAndObserve
-> AbstractCommand.executeCommandWithSpecifiedIsolation 创建一个新线程来执行
-> AbstractCommand.getUserExecutionObservable
-> HystrixCommand.getExecutionObservable

限流

Hystrix的限流限制的是并发量,其实和隔离特性的实现方式一样,可以通过调整信号量或线程池大小来实现限流。

熔断

熔断是一种异常处理机制,如果接口的失败率达到了阈值就触发降级,
当失败率达到阈值自动触发降级(如因网络故障/超时造成的失败率高),之后熔断器会尝试进行恢复。
Hystrix 会为降级的接口维持一个熔断状态,状态扭转如下图所示:
熔断状态扭转

  1. 熔断关闭状态(Closed)
    服务没有故障时,熔断器所处的状态,对调用方的调用不做任何限制。
  2. 熔断开启状态(Open)
    在固定时间窗口内(Hystrix 默认是 10 秒),接口调用出错比率达到一个阈值(Hystrix 默认为 50%),会进入熔断开启状态。进入熔断状态后,后续对该服务接口的调用不再经过网络,直接执行本地的 fallback 方法。
  3. 半熔断状态(Half-Open)
    在进入熔断开启状态一段时间之后(Hystrix 默认是 5 秒),熔断器会进入半熔断状态。所谓半熔断就是尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。如果成功率达到预期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断关闭状态。

Hystrix 使用 HystrixCircuitBreaker 这个类来维持状态:
AbstractCommand.applyHystrixSemantics
-> HystrixCircuitBreaker.attemptExecution 是否已开启熔断(OPEN),如果超过时间窗口则切换到半熔断状态(HALF_OPEN)
AbstractCommand.executeCommandAndObserve
-> HystrixCircuitBreaker.markSuccess / markNonSuccess

降级(Fallback)

降级是一种状态,当一个接口被降级后,再对这个接口进行调用会直接走降级逻辑。可以触发降级的条件一般是调用超时(比如网络故障、超时)或资源不足(线程或信号量)。但是代码中会更复杂一些,具体地说,触发降级的条件包括:

  1. run()方法抛出非 HystrixBadRequestException 异常
    AbstractCommand.executeCommandAndObserve
    -> handleFallback 是一个回调接口匿名实现类的对象,会在出现异常时被调用
    HystrixBadRequestException 表示一类可以被忽略的异常,在 AbstractHystrixCommand 和 GenericObservableCommand 可以抛出,可以在 HystrixCommand 注解上的 ignoreExceptions 中声明这些可以被忽略的异常。
  2. run()方法调用超时
    对超时的异常检测和上边的一样,但是抛出的位置不一样。
    AbstractCommand.executeCommandAndObserve
    -> Observable.lift 添加超时操作 HystrixObservableTimeoutOperator
    -> HystrixTimer.addTimerListener 添加一个 TimerListener 用于判断执行时间是否达到阈值,若超过则抛出 HystrixTimeoutException
    -> ScheduledThreadPoolExecutor.scheduleAtFixedRate 定时触发滴答(tick)
    可以看到超时检测最终还是通过 Scheduled 线程池来实现的。
  3. 熔断器开启拦截调用
    AbstractCommand.applyHystrixSemantics
    -> HystrixCircuitBreaker.attemptExecution
  4. 线程池/队列/信号量是否跑满
    在《隔离》那一节中已经说明了代码的位置。
  5. 没有实现 getFallback 的 Command 将直接抛出异常,fallback 降级逻辑调用成功直接返回,降级逻辑调用失败抛出异常
    HystrixCommand.getFallback 直接抛出了一个 UnsupportedOperationException 异常。

缓存

提供了请求缓存、请求合并实现。支持实时监控、报警、控制(修改配置)
结果缓存这个功能我觉得应该由幂等框架来做。

参考

  1. 漫画:什么是服务熔断?
  2. Hystrix 使用入门手册(中文)
  3. 从源码看 hystrix 的工作原理
  4. hystrix 适用场景
  5. Github - star2478/java-hystrix
  6. Netflix / Hystrix