Hystrix使用
Hystrix的使用方式有两种:原生和注解
首先引入依赖Change Log:
1 | <dependency> |
Hystrix提供了非常便捷的封装 – 两个可继承的抽象类HystrixCommand
/HystrixObservableCommand
。只需继承它们,实现run()
/construct()
方法,在方法中实现对需要隔离的外部依赖和逻辑,即可享受Hystrix带来的诸多好处。HystrixCommand
有四种方法供不同场景需求:execute()
/queue()
/observe()
/toObservable()
,HystrixObservableCommand
提供observe()
/toObservable()
- 四种方法的功能:
- execute():以同步堵塞方式执行
run()
- queue():以异步非堵塞方式执行
run()
- observe():事件注册前执行
run()
/construct()
。HystrixCommand
会创建新线程非阻塞执行run()
,HystrixObservableCommand
则会在调用程序里执行construct()
。 - toObservable():事件注册后执行
run()
/construct()
。调用toObservable()
会直接返回一个就直接返回一个Observable<R>
对象,而不去执行。当执行subscribe时,才会去执行逻辑run()
方法中的逻辑或者HystrixObservableCommand
中constrcut()
返回的Observable中的逻辑。与上面一样,HystrixCommand
创建一个线程来执行逻辑,而HystrixObservableCommand
会直接使用调用线程执行。
- execute():以同步堵塞方式执行
首先,来简单看一看 Hystrix官方Wiki给出的例子(movie-ocs中的 xxx)
1 | public class CommandHelloWorld extends HystrixCommand<String> { |
刚说了还有一种继承 HystrixObservableCommand
,相对于HystrixCommand
它只提供了observe()
和toObservable()
方法。那么它存在的意义是啥?
来看看它们的差别:
表面的差别:除了名字不一样,前者的业务逻辑写在
run()
里,并直接返回结果;后者的命令逻辑写在construct()
返回Observable<R>
;前者默认是线程隔离,后者是信号量隔离,换种说法:前者在新的线程中执行,后者是调用线程中执行。
前者一个实例只能向调用程序发送单条结果数据;后者一个
Observable
, 其可以发送多条数据。
1 | public class ObservableCommandHelloWorld extends HystrixObservableCommand<String> { |
3. fallback(降级)
降级是指在系统出现问题时,系统能提供一些备用的方法,常用的如返回一些默认的值或者执行一些降级逻辑。
Hystrix非常优雅地支持降级,只需要在命令类中添加降级方法getFallback()
或者resumeWithFallback()
。在主命令失败时,可以返回一些默认的结果。
1 |
|
针对ObservableCommandHelloWorld
,我们可以添加降级逻辑如下:
1 |
|
当发生如下几种情况时,会调用降级逻辑:
- 非
HystrixBadRequestException
异常,业务逻辑抛出的异常。对于HystrixBadRequestException
,其可以被用来包装如参数非法或者非业务逻辑错误等异常,且其不会被记录到失败的度量数据中。 - 业务逻辑超时异常;
- 熔断器打开时;
- 线程池满或者信号量满的情况会。
##4. 请求缓存
Hystrix支持在同一个上下文中缓存请求结果。当多次对相同key的资源请求时,只会在第一次发出请求,而后面的请求都会利用前面得到的结果。此处上下文可以是一次客户端的请求。只需要实现getCacheKey()
方法即可。
1 | public class CommandUsingRequestCache extends HystrixCommand<Boolean> { |
对于Web服务,我们可以在Filter中建立上下文,请求结束时,关闭上下文。
1 | public class HystrixContextFilter implements Filter { |
##4. 隔离策略
Hystrix应用舱壁模式来隔离依赖,限制对依赖的并发访问。
舱壁:货船为了进行防止漏水和火灾的扩散,会将货仓分隔为多个,当发生灾害时,将所在货仓进行隔离就可以降低整艘船的风险。
Hystrix 有两种隔离策略:线程隔离和信号量隔离
- 线程隔离:每个外部依赖在隔离的线程中执行,将这些对外部以来的调用从调用线程中隔离开,从而不会影响调用者的整个流程。Hystrix底层通过为每个依赖建一个线程池的方式来达到当此依赖的线程池耗尽时不会影响其他依赖。(对于Netflix为什么会用线程和线程池来实现隔离,以及其好处和劣势,对于会引起性能损耗问题,官方也给出了实验结果:How-it-Works#Isolation)
- 信号量隔离:一般用来控制对给定依赖的并发请求数量。通过使用信号量的方式来控制负载。但是它不允许超时和非阻塞请求。如果足够信任客户端且仅仅想限制负载时,可以使用此方法。
HystrixCommand
和HystrixObservableCommand
支持在两个地方实施信号量隔离:Fallback
和Execution
配置Command为信号隔离还是线程隔离:
以下为线程隔离的方式,同时可以设置线程池的属性
1 | public class CommandUsingThreadsolation extends HystrixCommand<String> { |
5. 熔断机制
什么是熔断?
熔断是指:在因某些原因或者依赖使得系统出现过载现象时,为了防止其拖垮整个系统而采取的措施。当熔断开关打开时,系统将关闭对某一服务的请求,闭而走降级逻辑或向调用者抛错。
Hystrix 中实现了断路器的逻辑,并为每个 commandKey 标识的服务创建一个断路器。HystrixCommand
和HystrixObserableCommand
将断路器作为其属性,作用在命令执行过程中。默认情况下,请求都会走熔断判断逻辑。当然可以强制关闭它 circuitBreaker.forceClosed=true
,使熔断器失效;或者强制开启它 circuitBreaker.forceOpen=true
,使得所有的请求都走降级逻辑。
断路器共有三个状态:OPEN
、CLOSE
、HALF-OPEN
OPEN
开启状态下,所有的请求都被降级;CLOSE
关闭状态下,请求都正常执行;HALF-OPEN
半开状态,断路器为开启状态一段时间后会放过一个请求:如果这个请求失败则将断路器依然为OPEN
状态,成功则将管路器设置为CLOSE
状态。
先来简单看下Hystrix中断路器是怎么起作用的:
命令执行结果统计:
命令执行完成后,会生成很多计量信息(命令执行结果类型,比如:一段时间内,命令执行成功、失败、超时以及被拒绝的数量)。
这些度量信息是怎么记录的:Hystrix使用滑动窗口的方式来保存度量数据,它只会保存最近rollingStats.timeInMilliseconds
段时间内的所有命令执行结果信息,并且为了更好地使用它们,会将整个时间段的度量分为rollingStats.numBuckets
个桶来保存。
断路器执行时,会根据这些数据来计算错误率供断路器使用
断路器的CLOSE
状态向OPEN
状态转换时,需要满足以下条件:
- 滑动窗口周期内的总请求数量大于阈值
HystrixCommandProperties.circuitBreakerRequestVolumeThreshold()
; - 滑动窗口周期内请求错误率超过设置的阈值
HystrixCommandProperties.circuitBreakerErrorThresholdPercentage()
断路器由OPEN
→ HALF-OPEN
→ CLOSE
转换时,需要满足一下条件:
在断路器开启HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()
ms后,会放过一个请求,此时断路器状态更改为HALF-OPEN
状态,此请求成功时,则由HALF-OPEN
状态转向CLOSE
。
配置示例如下:
1 | public class CommandUsingCircuitBreaker extends HystrixCommand<String> { |
7. 请求合并(HystrixCollapser)
HystrixCollapser
用于将多个请求合并成一个请求发出。
将多个请求合并成一个请求的好处是:它可以减少线程的数量和网络连接的数量,减少系统代价。HystrixCollapser
使用全自动的方式来实现合并请求,而不需要开发人员以硬编码的形式来收工支持批量请求。
但是它也是有一定的代价:它需要等待一个窗口期(timerDelayInMilliseconds
默认10ms)来接受批量请求,然后将请求批量发送。
因此对于时延较低的服务不推荐使用此方法。
1 | public class CommandCollapserGetValueForKey extends HystrixCollapser<List<String>, String, Integer> { |
对于更详细的配置信息,可以参考官方文档
注解使用
Java相对于其他语言有个比较大的优势是反射和注解。几乎所有的框架(Spring、MyBatis等)都将注解应用到了极致。上面的示范也展现了接入Hystrix需要做很多硬编码工作,且对代码具有较大的侵入性。因此开源社区的贡献者就开发了Hystrix的注解接入方式(hystrix-javanica),旨在帮助开发人员提高接入效率。
首先,因为它不是官方的核心功能,所以需要另外导入依赖。
1 | <dependency> |
其次,hystrix-javanica
中使用AspectJ
来实施切面功能。所以需要进行如下配置:
1 | <aop:aspectj-autoproxy proxy-target-class="true" /> |
使用方式:
同步方式:
1 | public class UserService { |
异步方式:
1 | "UserGroup", commandKey = "GetUserByIdCommand") (groupKey= |
Fallback
1 | "defaultUser") (fallbackMethod = |
Hystrix Collapser
1 | /** Asynchronous Execution */ |
动态配置
Hystrix默认就支持动态配置,在运行时改变参数。Hystrix的配置是用Archaius来实现。
它支持以特定的时间周期来执行配置拉取和替换,使用它的动态配置只需要实现PolledConfigurationSource
接口,并在其中实现配置拉取的逻辑即可。
如下,每3000秒一次拉取一次更新的配置:
1 | public class HystrixDynamicConfigurationManager implements InitializingBean { |
Dashboard
Hystrix可以做到实时地监控命令的度量信息,观察命令的执行情况。
通过实时观察这些信息,我们可以做实时调整设置(如超时时间等)、观察到系统的运行情况:延时、超时数量、断路器是否打开等,当系统出现故障时可以第一时间做出应对。
接入Dashboard我们需要两步走:
第一步:将系统的度量数据输出。Hystrix社区已有贡献者开发了相关功能,我们只需要引入如下依赖并在在web.xml
中插入相应Servlet即可在浏览器中
1 | <dependency> |
以下为其输出的信息:
1 | ping: |
第二步:运行Turbine,使用Turbine将信息聚合并以实时图表的形式展现出来。Hystrix官方也给出了安装指导信息
参考:
Hystrix Wiki
1. How-to-use
2. DashBoard
3. Hystrix使用入门手册