断路器HystrixCircuitBreaker有三种状态:
- CLOSE:关闭
- OPEN:打开
- HALF_OPEN:半开
下图展示了HystrixCommand
或HystrixObservableCommand
如何与HystrixCircuitBreaker
进行交互,以及HystrixCircuitBreaker
的决策逻辑过程,包括熔断器内部计数器如何工作。
线路的开路闭路详细逻辑如下:
- 假设线路内的容量(请求QPS)达到一定阈值(通过
HystrixCommandProperties.circuitBreakerRequestVolumeThreshold()
配置) - 同时,假设线路内的错误率达到一定阈值(通过
HystrixCommandProperties.circuitBreakerErrorThresholdPercentage()
配置) - 熔断器将从
闭路
转换成开路
- 若此时是
开路
状态,熔断器将短路后续所有经过该熔断器的请求,这些请求直接走失败回退逻辑
- 经过一定时间(即
休眠窗口
,通过HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()
配置),后续第一个请求将会被允许通过熔断器(此时熔断器处于半开
状态),若该请求失败,熔断器将又进入开路
状态,且在休眠窗口内保持此状态;若该请求成功,熔断器将进入闭路
状态,回到逻辑1循环往复
HystrixCircuitBreaker
com.netflix.hystrix.HystrixCircuitBreaker
,Hystrix断路器接口。定义接口如下代码:
1 | public interface HystrixCircuitBreaker { |
allowRequest()
和attemptExecution()
方法,方法目的基本类似,差别在于当断路器满足尝试关闭条件时,前者不会修改断路器的状态(CLOSE
=> HALF-OPEN
),而后者会。
HystrixCircuitBreaker有两个子类实现:
- NoOpCircuitBreaker:空的断路器实现,用于不开启断路器功能的情况
- HystrixCircuitBreakerImpl:完整的断路器实现
在AbstractCommand
创建时,初始化HystrixCircuitBreaker
,代码如下:
1 | abstract class AbstractCommand<R> implements HystrixInvokableInfo<R>, HystrixObservable<R> { |
- 当
HystrixCommandProperties.circuitBreakerEnabled = true
时,即断路器功能开启,使用Factory获得HystrixCircuitBreakerImpl
对象 - 当
HystrixCommandProperties.circuitBreakerEnabled = false
时,即断路器功能关闭,创建NoOpCircuitBreaker
对象。
HystrixCircuitBreaker.Factory
com.netflix.hystrix.HystrixCircuitBreaker.Factory
,HystrixCircuitBreaker工厂,主要用于:
- 创建
HystrixCircuitBreaker
对象,目前只创建HystrixCircuitBreakerImpl
HystrixCircuitBreaker
容器,基于HystrixCommandKey维护了HystrixCircuitBreaker单例对象的映射。代码如下:
1 | private static ConcurrentHashMap<String, HystrixCircuitBreaker> circuitBreakersByCommand = new ConcurrentHashMap<String, HystrixCircuitBreaker>(); |
HystrixCircuitBreakerImpl
com.netflix.hystrix.HystrixCircuitBreaker.HystrixCircuitBreakerImpl
,完整的断路器实现
构造方法
1 | class HystrixCircuitBreakerImpl implements HystrixCircuitBreaker { |
subscribeToStream
subscribeToStream
方法向Hystrix Metrics对请求量统计Observable发起订阅。代码如下:
1 | private Subscription subscribeToStream() { |
- 向Hystrix Metrics对请求量统计Observable发起订阅。这里的Observable基于RxJava Window操作符。简单来说,固定间隔,
onNext()
方法将不断被调用,每次计算断路器的状态。 onNext()
方法- 判断周期内(可配,
HystrixCommandProperties.default_metricsRollingStatisticalWindow = 10000ms
),总请求数超过一定量(可配,HystrixCommandProperties.circuitBreakerRequestVolumeThreshold = 20
) - 错误请求占总请求数超过一定比例(可配,
HystrixCommandProperties.circuitBreakerErrorThresholdPercentage = 50%
) - 满足断路器打开条件,CAS将状态从
CLOSED
修改为OPEN
,并设置打开时间(circuitOpened
)
- 判断周期内(可配,
Hystrix Metrics对请求量统计Observable使用了两种RxJava Window操作符:
Observable.window(timespan, unit)
方法,固定周期(可配,HystrixCommandProperties.metricsHealthSnapshotIntervalInMilliseconds = 500ms
),发射Observable窗口。Observable.window(count, skip)
方法,每发射一次(skip
)Observable忽略count
(可配,HystrixCommandProperties.circuitBreakerRequestVolumeThreshold = 20
)个数据项
目前该方法有两处调用:
- 构造方法,在创建
HystrixCircuitBreakerImpl
时,向Hystrix Metrics对请求量统计Observable发起订阅。固定间隔计算断路器是否要关闭 markSuccess
,清空Hystrix Metrics对请求量统计Observable的统计信息,取消原有订阅,并发起新的订阅
attemptExecution
如下是AbstractCommand.applyHystrixSemantics
方法,对HystrixCircuitBreakerImpl.attemptExecution
方法的调用的代码:
1 | private Observable<R> applyHystrixSemantics(final AbstractCommand<R> _cmd) { |
使用HystrixCircuitBreakerImpl.attemptExecution
方法,判断是否可以执行正常逻辑。代码如下:
1 | public boolean attemptExecution() { |
- 当
HystrixCommandProperties.circuitBreakerForceOpen = true
(默认值:false
),即断路器强制打开,返回false
。当该配置接入配置中心后,可以动态实现打开熔断。为什么会有该配置?当HystrixCircuitBreaker
创建完成后,无法动态切换NoOpCircuitBreaker
和HystrixCircuitBreakerImpl
,通过该配置以实现类似效果。 - 当
HystrixCommandProperties.circuitBreakerForceClose = true
(默认值:false
),即断路器强制关闭,返回true
。当该配置接入配置中心后,可以动态实现关闭熔断。为什么会有该配置?当HystrixCircuitBreaker
创建完成后,无法动态切换NoOpCircuitBreaker
和HystrixCircuitBreakerImpl
,通过该配置以实现类似效果。 - 断路器打开时间(
circuitOpened
)为空,返回true
- 调用
isAfterSleepWindow
方法,判断是否满足尝试调用正常逻辑的间隔时间。当满足时,使用CAS方法修改断路器状态(OPEN
=>HALF_OPEN
),从而保证有且仅有一个线程能够尝试调用正常逻辑
isAfterSleepWindow
方法代码如下:
1 | private boolean isAfterSleepWindow() { |
在当前时间超过断路器打开时间HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds
(默认值:5000ms
),返回true
。
markSuccess
当尝试调用正常逻辑成功时,调用markSuccess
方法,关闭断路器。
1 | public void markSuccess() { |
- 使用CAS方式,修改断路器状态(
HALF_OPEN
=>CLOSED
) - 清空Hystrix Metrics对请求量统计Observable的统计信息
- 取消原有订阅,发起新的订阅
- 设置断路器打开时间为空
以下两处调用markSuccess
方法:
markEmits
markOnCompleted
markNonSuccess
当尝试调用正常逻辑失败时,调用markNonSuccess
方法,重新打开断路器。
1 | public void markNonSuccess() { |
- 使用CAS的方式,修改断路器的状态(
HALF_OPEN
=>OPEN
) - 设置断路器打开时间为当前时间。这样
attemptExecution()
过一段时间,可以再次尝试执行正常逻辑
以下两处调用了markNonSuccess
方法:
handleFallback
unsubscribeCommandCleanup
allowRequest
allowRequest
方法和attemptExecution
方法,目的基本类似,差别在于当断路器满足尝试关闭条件时,前者不会修改断路器的状态(CLOSE
=> HALF-OPEN
),而后者会。
isOpen
isOpen
方法比较简单:
1 | public boolean isOpen() { |
断路器测试
对断路器的测试,我们选择比较简单的方式,直接调用Hystrix的命令:
1 | public class CircuitBreakerCommand extends HystrixCommand<String> { |
执行结果如下:
1 | call times:1 result: running: isCircuitBreakerOpen: false |
我们看到,前10此命令执行有两次失败,于是熔断器被打开,11到20次执行全部快速失败。5s后熔断器关闭,命令可以再次尝试执行。
总结
总体来说,Hystrix的断路器是一个防止重复并发请求失败服务的机制,它的执行流程如下:
- 在一定时间内(HystrixCommandProperties.metricsRollingStatisticalWindowInMilliseconds,默认为10000ms),请求次数达到一定阈值(HystrixCommandProperties.circuitBreakerRequestVolumeThreshold,默认为20),且错误率达到一定阈值(HystrixCommandProperties.circuitBreakerErrorThresholdPercentage,默认为50%),熔断器将从闭路转换成开路。开路状态下,所有请求直接走失败回退逻辑。
- 经过一定的时间(休眠窗口,HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds,默认为5000ms),后续第一个请求将会被允许通过熔断器(半开状态),若该请求失败,熔断器又进入开路状态,且在休眠窗口内保持此状态;若该请求成功,熔断器将进入闭路状态。
http://youdang.github.io/2016/02/05/translate-hystrix-wiki-how-it-works/#%E8%AF%B7%E6%B1%82%E5%90%88%E5%B9%B6
https://github.com/YunaiV/Blog/blob/master/Hystrix/2018_11_08_Hystrix%20%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%20%E2%80%94%E2%80%94%20%E6%96%AD%E8%B7%AF%E5%99%A8%20HystrixCircuitBreaker.md
https://www.jianshu.com/p/14958039fd15