Hystrix 原理分析
降级有几种方案
降级分类
降级主要分手动降级和自动降级,手动降级其实就是在代码里加上一些开关把功能直接关闭,自动降级可以分为以下几种:
- 超时降级
配置好超时时间和超时重试次数和机制,并使用异步机制探测恢复情况。 - 失败次数降级
失败次数达到阈值自动降级,同样要使用异步机制探测回复情况。 - 故障降级
如要调用的远程服务挂掉了(网络故障、DNS 故障、HTTP 服务返回错误的状态码和 RPC 服务抛出异常),则可以直接降级。
和上面那种失败次数降级原理类似,只是需要区分错误类型。 - 限流降级
单位时间内调用次数超过阈值,可以使用暂时屏蔽的方式来进行短暂的屏蔽。
降级处理方案
降级后,在代码层面,可以进行的处理策略如下:
- 抛异常
- 返回 NULL
- 调用 Mock 数据
- 调用 Fallback 处理逻辑
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 会为降级的接口维持一个熔断状态,状态扭转如下图所示:
- 熔断关闭状态(Closed)
服务没有故障时,熔断器所处的状态,对调用方的调用不做任何限制。 - 熔断开启状态(Open)
在固定时间窗口内(Hystrix 默认是 10 秒),接口调用出错比率达到一个阈值(Hystrix 默认为 50%),会进入熔断开启状态。进入熔断状态后,后续对该服务接口的调用不再经过网络,直接执行本地的 fallback 方法。 - 半熔断状态(Half-Open)
在进入熔断开启状态一段时间之后(Hystrix 默认是 5 秒),熔断器会进入半熔断状态。所谓半熔断就是尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。如果成功率达到预期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断关闭状态。
Hystrix 使用 HystrixCircuitBreaker 这个类来维持状态:
AbstractCommand.applyHystrixSemantics
-> HystrixCircuitBreaker.attemptExecution 是否已开启熔断(OPEN),如果超过时间窗口则切换到半熔断状态(HALF_OPEN)
AbstractCommand.executeCommandAndObserve
-> HystrixCircuitBreaker.markSuccess / markNonSuccess
降级(Fallback)
降级是一种状态,当一个接口被降级后,再对这个接口进行调用会直接走降级逻辑。可以触发降级的条件一般是调用超时(比如网络故障、超时)或资源不足(线程或信号量)。但是代码中会更复杂一些,具体地说,触发降级的条件包括:
- run()方法抛出非 HystrixBadRequestException 异常
AbstractCommand.executeCommandAndObserve
-> handleFallback 是一个回调接口匿名实现类的对象,会在出现异常时被调用
HystrixBadRequestException 表示一类可以被忽略的异常,在 AbstractHystrixCommand 和 GenericObservableCommand 可以抛出,可以在 HystrixCommand 注解上的 ignoreExceptions 中声明这些可以被忽略的异常。 - run()方法调用超时
对超时的异常检测和上边的一样,但是抛出的位置不一样。
AbstractCommand.executeCommandAndObserve
-> Observable.lift 添加超时操作 HystrixObservableTimeoutOperator
-> HystrixTimer.addTimerListener 添加一个 TimerListener 用于判断执行时间是否达到阈值,若超过则抛出 HystrixTimeoutException
-> ScheduledThreadPoolExecutor.scheduleAtFixedRate 定时触发滴答(tick)
可以看到超时检测最终还是通过 Scheduled 线程池来实现的。 - 熔断器开启拦截调用
AbstractCommand.applyHystrixSemantics
-> HystrixCircuitBreaker.attemptExecution - 线程池/队列/信号量是否跑满
在《隔离》那一节中已经说明了代码的位置。 - 没有实现 getFallback 的 Command 将直接抛出异常,fallback 降级逻辑调用成功直接返回,降级逻辑调用失败抛出异常
HystrixCommand.getFallback 直接抛出了一个 UnsupportedOperationException 异常。
缓存
提供了请求缓存、请求合并实现。支持实时监控、报警、控制(修改配置)
结果缓存这个功能我觉得应该由幂等框架来做。