2017-12-04:待重新总结。。。
这篇文章里我们来总结一下 Netflix Hystrix 的工作流程(版本为 1.4.x)。这是官方提供的流程图(来自 GitHub):
工作流程
我们来根据流程图来分析一下工作流程。
首先我们需要创建一个 HystrixCommand
或 HystrixObservableCommand
实例来代表向其它组件发出的操作请求(指令),然后通过相关的方法执行操作指令。这里有4个方法,前两个对应HystrixCommand
,后两个对应HystrixObservableCommand
:
execute()
:阻塞型方法,返回单个结果(或者抛出异常)queue()
:异步方法,返回一个Future
对象,可以从中取出单个结果(或者抛出异常)observe()
和toObservable()
都返回对应的Observable
对象,代表(多个)操作结果。注意observe
方法在调用的时候就开始执行对应的指令(hot observable 加了层 buffer 代理),而toObservable
方法相当于是observe
方法的lazy版本,当我们去subscribe
的时候,对应的指令才会被执行并产生结果
|
|
从底层实现来讲,HystrixCommand
也是利用Observable
实现的(看Hystrix源码的话可以发现里面大量使用了RxJava),尽管它只返回单个结果。HystrixCommand
的queue
方法实际上是调用了toObservable().toBlocking().toFuture()
,而execute
方法实际上是调用了queue().get()
。
执行操作指令时,Hystrix首先会检查缓存内是否有对应指令的结果,如果有的话,将缓存的结果直接以Observable
对象的形式返回。如果没有对应的缓存,Hystrix会检查Circuit Breaker的状态。如果Circuit Breaker的状态为开启状态,Hystrix将不会执行对应指令,而是直接进入失败处理状态(图中8 Fallback)。如果Circuit Breaker的状态为关闭状态,Hystrix会继续进行线程池、任务队列、信号量的检查(图中5),确认是否有足够的资源执行操作指令。如果资源满,Hystrix同样将不会执行对应指令并且直接进入失败处理状态。
如果资源充足,Hystrix将会执行操作指令。操作指令的调用最终都会到这两个方法:
HystrixCommand.run()
HystrixObservableCommand.construct()
如果执行指令的时间超时,执行线程会抛出TimeoutException
异常。Hystrix会抛弃结果并直接进入失败处理状态。如果执行指令成功,Hystrix会进行一系列的数据记录,然后返回执行的结果。
同时,Hystrix会根据记录的数据来计算失败比率,一旦失败比率达到某一阈值将自动开启Circuit Breaker。
最后我们再来看一下Hystrix是如何处理失败的。如果我们在Command中实现了HystrixCommand.getFallback()
方法(或HystrixObservableCommand.resumeWithFallback()
方法,Hystrix会返回对应方法的结果。如果没有实现这些方法的话,从底层看Hystrix将会返回一个空的Observable
对象,并且可以通过onError
来终止并处理错误。从上层看:
execute
方法将会抛出异常queue
方法将会返回一个失败状态的Future
对象observe()
和toObservable()
方法都会返回上述的Observable
对象
HystrixCircuitBreaker源码分析
Hystrix中的Circuit Breaker的实现比较明了。整个HystrixCircuitBreaker
接口一共有三个方法和三个静态类:
其中allowRequest()
方法表示是否允许指令执行,isOpen()
方法表示断路器是否为开启状态,markSuccess()
用于将断路器关闭。
Factory
静态类相当于Circuit Breaker Factory,用于获取相应的HystrixCircuitBreaker
。我们来看一下其实现:
|
|
Hystrix在Factory
类中维护了一个ConcurrentHashMap
用于存储与每一个HystrixCommandKey
相对应的HystrixCircuitBreaker
。每当我们通过getInstance
方法从中获取HystrixCircuitBreaker
的时候,Hystrix首先会检查ConcurrentHashMap
中有没有对应的缓存的断路器,如果有的话直接返回。如果没有的话就会新创建一个HystrixCircuitBreaker
实例,将其添加到缓存中并且返回。
HystrixCircuitBreakerImpl
静态类是HystrixCircuitBreaker
接口的实现。我们可以看到HystrixCircuitBreakerImpl
类中有四个成员变量。其中properties
是对应HystrixCommand
的属性类,metrics
是对应HystrixCommand
的度量数据类。由于会工作在并发环境下,我们用一个AtomicBoolean
类型的变量circuitOpen
来代表断路器的状态(默认是false
代表关闭,这里没有特意实现Half-Open这个状态),并用一个AtomicLong
类型的变量circuitOpenedOrLastTestedTime
记录着断路恢复计时器的初始时间,用于Open状态向Close状态的转换。
我们首先来看一下isOpen
方法的实现:
|
|
首先通过circuitOpen.get()
获取断路器的状态,如果是开启状态(true
)则返回true
。否则,Hystrix会从Metrics数据中获取HealthCounts
对象,然后检查对应的请求总数(totalCount
)是否小于属性中的请求容量阈值(circuitBreakerRequestVolumeThreshold
),如果是的话表示断路器可以保持关闭状态,返回false
。如果不满足请求总数条件,就再检查错误比率(errorPercentage
)是否小于属性中的错误百分比阈值(circuitBreakerErrorThresholdPercentage
,默认 50),如果是的话表示断路器可以保持关闭状态,返回 false
;如果超过阈值,Hystrix会判定服务的某些地方出现了问题,因此通过CAS操作将断路器设为开启状态,并记录此时的系统时间作为定时器初始时间,最后返回 true
。
我们再来看一下判断Open状态下计时器的实现方法allowSingleTest
:
|
|
首先获取断路恢复计时器记录的初始时间circuitOpenedOrLastTestedTime
,然后判断以下两个条件是否同时满足:
- 断路器的状态为开启状态(
circuitOpen.get() == true
) - 当前时间与计时器初始时间之差大于计时器阈值
circuitBreakerSleepWindowInMilliseconds
(默认为 5 秒)
如果同时满足的话,表示可以从Open
状态向Close
状态转换。Hystrix会通过CAS操作将circuitOpenedOrLastTestedTime
设为当前时间,并返回true
。如果不同时满足,返回false
,代表断路器关闭或者计时器时间未到。
有了这个函数以后,我们再来看一下allowRequest
的实现:
|
|
非常直观。首先先读取属性中的强制设定值(可以强制设定状态),如果没有设定的话,就判断断路器是否关闭或者断路恢复计时器是否到达时间,只要满足其中一个条件就返回true
,即允许执行操作指令。
最后就是markSuccess
方法了,它用于关闭断路器并重置统计数据。代码非常直观,就不多说了:
|
|
Hystrix的Circuit Breaker可以用以下的图来总结:
至于Hystrix在底层执行Command时是如何利用HystrixCircuitBreaker
的,可以看AbstractCommand
类中toObservable
方法和getRunObservableDecoratedForMetricsAndErrorHandling
方法的源码,后边再总结。