https://kiswo.com/article/1031
导读: 在上一篇,已经对 Hystrix 的原理进行了了解。为了加深理解和快速实际应用,编写了一些测试代码对几个关键点进行测试验证。有些结论,是官网已经说明的。另一些是通过测试或其他同行使用得出的结论。
通过测试代码 hystrix-example,可以对Hystrix的细节进行分析和验证。
1、执行Command
HystrixCommand
的执行流程如下:
- execute()是同步堵塞的,它调用了queue().get()方法,execute()执行完后,会创建一个新线程运行run()
- queue()是异步非堵塞的,它调用了toObservable().toBlocking().toFuture()方法,queue()执行完后,会创建一个新线程运行run()。Future.get()是堵塞的,它等待run()运行完才返回结果
- observe()是异步的,是热响应调用,它调用了toObservable().subscribe(subject)方法,observe()执行完后,会创建一个新线程运行run()。toBlocking().single()是堵塞的,需要等run()运行完才返回结果
- toObservable()是异步的,是冷响应调用,该方法不会主动创建线程运行run(),只有当调用了toBlocking().single()或subscribe()时,才会去创建线程运行run()
注意:
- 1、同一个HystrixCommand对象只能执行一次run()
- 2、observe()中,toBlocking().single()与subscribe()是可以共存的,因为run()是在observe()中被调用的,只调用了一次
- 3、toObservable()中,toBlocking().single()与subscribe()不可共存,因为run()是在toBlocking().single()或subscribe()中被调用的;如果同时存在toBlocking().single()和subscribe(),相当于调用了2次run(),会报错
2、回退降级
- 继承
,重写getFallback()方法,该方法的响应要快,尽量不要有网络依赖HystrixCommand
- 如果有网络依赖,建议采取多次降级,即在getFallback()方法实例化
,并执行CommandHystrixCommand
- 注意getFallback()的异常捕捉,如果getFallback(),会直接中断
的流程HystrixCommand
3、Command Name、Group、Thread-Pool
- CommandKey/CommandName是一个依赖服务的command标识
- GroupKey是将报告,警报,仪表板或团队/库所有权等命令组合在一起。一般可以根据服务模块或第三方客户端来分配GroupKey,一个GroupKey下可以有多个CommandKey
- ThreadPoolKey用于监视,度量标准发布,缓存和其他此类用途的HystrixThreadPool。可以一个CommandKey绑定一个ThreadPoolKey用,这样多个线程的CommandKey就会划分到同一个ThreadPoolKey
- 没有定义ThreadPoolKey时,ThreadPoolKey使用GroupKey,定义了ThreadPoolKey时,则使用定义值(采用线程策略隔离的情况下)
- command在执行run()时,会创建一个线程,该线程的名称是ThreadPoolKey和序列号的组合,序列号是该线程在线程池中的创建顺序
- 使用ThreadPoolKey的原因是多个command可能属于同一个所有权或逻辑功能『组』,但某些command又需要彼此隔离。
CommandName不设置名称的话,默认名称是从类名派生的:
getClass().getSimpleName();
要明确定义名称,通过HystrixCommand或HystrixObservableCommand构造函数传入:
public CommandHelloWorld(String name) {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld")));
this.name = name;
}
或者为了保存每个命令分配的Setter分配,你也可以像这样缓存Setter:
private static final Setter cachedSetter =
Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
.andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"));
public CommandHelloWorld(String name) {
super(cachedSetter);
this.name = name;
}
4、熔断器
同时满足以下条件,熔断器将打开:
1、整个链路请求数达到阀值(
circuitBreaker.requestVolumeThreshold
),默认情况下,10秒内请求数超过20次,则符合第一个条件。
2、在满足第一个条件的前提下,如果请求的错误数比例大于阀值(
circuitBreaker.errorThresholdPercentage
),则会打开熔断器,默认为50%。
如果熔断器处于打开状态,将会进入休眠期,在休眠期内,所有请求都将被拒绝,直接执行fallback逻辑。
根据
Metrics
的计算,可以判断熔断器的健康状态,从而决定是否应该关闭熔断器:
- 熔断器被打开后,根据
设置,会休眠一段时间,这段时间内的所有请求,都直接fallbackcircuitBreaker.sleepWindowInMilliseconds
- 休眠时间过后,Hystrix会将熔断器状态改为半开状态,然后尝试性的执行一次command,如果成功,则关闭熔断器,如果失败,继续打开熔断器,执行新的熔断周期
- 熔断器打开后,熔断器的健康检查指标会重置,重新开始计算
熔断器有以下几个特殊参数:
1、如果hystrix.command.default.circuitBreaker.enabled设置为false,将不使用断路器来跟踪健康状况,也不会在断路器跳闸时将其短路(即不会执行fallback)
2、如果hystrix.command.default.circuitBreaker.forceOpen设置为true,断路器将强制打开,所有请求将被拒绝,直接进入fallback
3、如果hystrix.command.default.circuitBreaker.forceClosed设置为true,断路器将强制关闭,无论错误百分比如何,都将允许请求(永远会执行run)
5、隔离策略
隔离策略分线程隔离和信号隔离。
5.1、线程隔离
HystrixCommand
默认采用的是线程隔离策略。通过服务容错与保护方案 — Hystrix和测试案例的
HystrixCommandDemo1
可以知道,当执行
construct()
或
run()
时,会创建一个线程。因为
Hystrix
用到了线程池,真实的流程是这样的:
- 1、执行
或construct()
时,先判断线程池中是否有空闲的线程(每个Command都可以拥有自己的线程池而不会互相影响)run()
- 2、如果没有空闲的,则看当前线程数是否达到
,如果达到,则需要排队,当队列值大于hystrix.threadpool.default.coreSize
, 会拒绝请求,执行回退逻辑,如果没有达到,则创建一个新的线程来执行hystrix.threadpool.default.maxQueueSize
- 3、如果有空闲的,则直接从空闲的线程中取出一个来执行
当然,我们也可以设置
hystrix.threadpool.default.maximumSize
,动态的控制线程的大小。该参数表示一个
HystrixCommand
可以创建的最大线程数,当线程池中的线程在
hystrix.threadpool.default.keepAliveTimeMinutes
时间内没有使用,则会关闭一些线程,使线程数等于在
hystrix.threadpool.default.coreSize
。
注意:
必须将
设置为
hystrix.threadpool.default.allowMaximumSizeToDivergeFromCoreSize
时,
true
才会生效
hystrix.threadpool.default.maximumSize
hystrix.threadpool.default.coreSize
的默认值为10,如果需要提高此值,按照以下公式计算:
最大线程数 = QPS * 平均响应时间(单位秒)* 99% + 缓存数
举例说明:
某个接口的单台服务器QPS为10000,平均响应时间为20ms
最大线程数:10000 * 0.02 * 0.99 + 4 = 202
Hystrix
官方建议尽量将最大线程数设置的小一些,因为它是减少负载并防止资源在延迟发生时被阻塞的主要工具。线程数能设置多大,有什么影响,这个需要根据自身业务情况和实际压测结果来衡量。
5.2、信号隔离
HystrixObservableCommand
默认采用的是信号隔离。
HystrixCommand
可以通过修改
hystrix.command.default.execution.isolation.strategy
参数调整为信号隔离。
- 信号隔离是对客户端请求线程的并发限制,采用信号隔离时,hystrix的线程相关配置将无效
- 当请求并发量大于
时,请求执行fallbackhystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests
- 当fallback的并发线程数大于
时,fallback将抛异常fallback execution rejectedhystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests
信号隔离策略下,执行
construct()
或
run()
时,使用的是应用服务的父级线程(如Tomcat容器线程)。所以,一定要设置好并发量,有网络开销的调用,不建议使用该策略,容易导致容器线程排队堵塞,从而影响整个应用服务。
6、请求合并
- 请求合并,可以减少通信消耗和线程数的占用,提高并发
- 请求合并会有延迟时间窗,会带来额外的开销,如果请求本身有较长的延迟,或合并的请求量较多时,请求合并会提升性能,反之,可能会降低性能
7、HystrixObservableCommand
HystrixObservableCommand
与
HystrixCommand
的区别:
- 1、它们两个是
执行Command的两种方式Hystrix
- 2、
的执行封装在run(),fallback处理封装在getFallBack();HystrixCommand
的执行封装在contruct(),fallback处理封装在resumeWithFallback()HystrixObservableCommand
- 3、
使用的信号隔离策略,所以,使用的是应用服务的父级线程调用contruct()HystrixObservableCommand
- 4、
在contruct()中可以定义多个onNext,当调用subscribe()注册成功后,将依次执行这些onNext(),后者只能在run()中返回一个值(即一个onNext)。可以理解为HystrixObservableCommand
一次只能发送单条数据返回,而HystrixCommand
一次可以发送多条数据返回HystrixObservableCommand
- 5、同
一样,HystrixCommand
使用observe(),toBlocking().single()或subscribe()可以共存,而使用toObservable(),则不能共存HystrixObservableCommand
参考:
- Hystrix how to use
- Hystrix Configuration
- Hystrix examples
- Hystrix常用功能介绍
- Hystrix使用入门手册