SpringCloud系列之-hystrix详解
hystrix是什么官方地址:https://github.com/Netflix/HystrixHystrix是由Netflix开源的一个服务隔离组件,通过服务隔离来避免由于依赖延迟、异常,引起资源耗尽导致系统不可用的解决方案。在分布式系统,尤其是在这个微服务盛行的年代,日常开发中我们一定会依赖各种服务,那么依赖的服务一定会出现调用失败的情况。Hystrix就是解决这种状况的一个工具,它通过提供
hystrix[hɪst'rɪks] 是什么
官方地址:https://github.com/Netflix/Hystrix
Hystrix是由Netflix开源的一个服务隔离组件,通过服务隔离来避免由于依赖延迟、异常,引起资源耗尽导致系统不可用的解决方案。在分布式系统,尤其是在这个微服务盛行的年代,日常开发中我们一定会依赖各种服务,那么依赖的服务一定会出现调用失败的情况。Hystrix就是解决这种状况的一个工具,它通过提供了逻辑上延时和错误容忍的解决力来协助我们完成分布式系统的交互。Hystrix 通过分离服务的调用点,阻止错误在各个系统的传播,并且提供了错误回调机制,这一系列的措施提高了系统的整体服务弹性。
hystrix的主要功能
Hystrix提供的功能主要体现如下:
- 资源隔离,包括线程池隔离和信号量隔离,避免某个依赖出现问题会影响到其他依赖;
- 熔断器,当请求失败率达到一定的阈值时,会打开断路器开关,直接拒绝后续的请求,并且具有弹性机制,在后端服务恢复后,会自动关闭断路器开关;
- 降级回退,当断路器开关被打开,服务调用超时/异常,或者资源不足(线程、信号量)会进入指定的fallback降级方法;
- 请求结果缓存,hystrix实现了一个内部缓存机制,可以将请求结果进行缓存,那么对于相同的请求则会直接走缓存而不用请求后端服务;
- 请求合并, 可以实现将一段时间内的请求合并,然后只对后端服务发送一次请求。
hystrix工作原理流程图:
1. 资源隔离
资源隔离的思想参考上述的舱壁隔离模式,在hystrix中提供了两种资源隔离策略:线程池隔离、信号量隔离。
线程池隔离:线程池隔离会为每一个依赖创建一个线程池来处理来自该依赖的请求,不同的依赖线程池相互隔离,就算依赖A出故障,导致线程池资源被耗尽,也不会影响其他依赖的线程池资源。
- 优点:支持排队和超时,支持异步调用。
- 缺点:线程的创建一个调度会造成一定的性能开销。
- 适用场景:适合耗时较长的接口场景,比如接口处理逻辑复杂,且与第三方中间件有交互,因为线程池模式的请求线程与实际转发线程不是同一个,所以可以保证容器有足够的线程来处理新的请求。
信号量隔离模式: 初始化信号量currentCount=0,每进来一个请求需要先将currentCount自增,再判断currentCount的值是否小于系统最大信号量,小于则继续执行,大于则直接返回,拒绝请求。
- 优点:轻量,无额外的开销,只是一个简单的计数器
- 缺点:不支持任务排队和主动超时;不支持异步调用
- 适用场景:适合能快速响应的接口场景,不适合一些耗时较长的接口场景,因为信号量模式下的请求线程与转发处理线程是同一个,如果接口耗时过长有可能会占满容器的线程数。
2. 熔断器
Hystrix是基于滚筒式来处理,每一秒会产生一个buckets,每产生一个新的buckets就会移除一个最老的buckets,默认是10秒一个窗口。buckets在内存中就是一种数据结构,每个buckets会记录Metrics的相关数据,比如成功、失败、超时、拒绝。当一个HystrixCommand进来后,会先通过allowRequest()方法判断是否允许通过该次请求,allowRequest()方法会通过isOpen判断断路器是否打开。断路器关闭,则允许通过该次请求;断路器打开,则会判断是否过了睡眠周期。没有过睡眠周期则返回false,拒绝通过该次请求,过了睡眠周期则会尝试放行。
isOpen()方法会按照(failure) / (success+failure)公式计算出失败率,如果失败率大于阈值,则会触发熔断。公式中的成功、失败的数据就来源于每10秒中一个窗口的滚筒数据。对于一个依赖调用,要么调用成功,要么调用失败(包括异常、超时、拒绝),这些调用结果都会记录到buckets中。对于调用成功结果来说,还会判断断路器开关是否打开,如果是打开状态的话,则会关闭断路器并重置相关的计数器。
3. 降级回退
降级,通常指事务高峰期,为了保证核心服务正常运行,需要停掉一些不太重要的业务,或者某些服务不可用时,执行备用逻辑从故障服务中快速失败或快速返回,以保障主体业务不受影响。 Hystrix提供的降级主要是为了容错,保证当前服务不受依赖服务故障的影响,从而提高服务的健壮性。
1)什么情况会进入服务降级逻辑
- 熔断器打开
- 线程池/信号量资源不足
- 执行依赖调用超时
- 执行依赖调用异常
2)降级回退方式
- Fail Fast快速失败:最普通的命令执行方法,命令没有重写降级逻辑。 如果命令执行发生任何类型的故障,它将直接抛出异常;
- Fail Fast无声失败:指在降级方法中通过返回null、空的结果集或其它类似的响应来完成;
- FallBack:Static:指在降级方法中返回静态默认值。 这不会导致服务以“无声失败”的方式被删除,而是导致默认行为发生。如:应用根据命令执行返回true / false执行相应逻辑,但命令执行失败,则默认为true;
- FallBack:Stubbed:当命令返回一个包含多个字段的复合对象时,适合以Stubbed 的方式回退;
- FallBack:Cache via Network:有时,如果调用依赖服务失败,可以从缓存服务(如redis)中查询旧数据版本。由于又会发起远程调用,所以建议重新封装一个Command,使用不同的ThreadPoolKey,与主线程池进行隔离;
- Primary+Secondary with FallBack:有时系统具有两种行为- 主要和次要,或主要和故障转移。主要和次要逻辑涉及到不同的网络调用和业务逻辑,所以需要将主次逻辑封装在不同的Command中,使用线程池进行隔离。为了实现主从逻辑切换,可以将主次command封装在外观HystrixCommand的run方法中,并结合配置中心设置的开关切换主从逻辑。由于主次逻辑都是经过线程池隔离的HystrixCommand,因此外观HystrixCommand可以使用信号量隔离,而没有必要使用线程池隔离引入不必要的开销。
4. 请求结果缓存
返回结果缓存,后续请求可以直接走缓存。 实际应用场景很少(除了简单的查询外)。
5.请求合并
1) HystrixCollapser
微服务架构中通常需要依赖多个远程的微服务,而远程调用中最常见的问题就是通信消耗与连接数占用。在高并发的情况之下,因通信次数的增加,总的通信时间消耗将会变得越来越长。同时,因为依赖服务的线程池资源有限,将出现排队等待与响应延迟的清况。
为了优化这两个问题,Hystrix 提供了HystrixCollapser来实现请求的合并,以减少通信消耗和线程数的占用。
HystrixCollapser实现了在 HystrixCommand之前放置一个合并处理器,将处于一个很短的时间窗(默认10毫秒)内对同一依赖服务的多个请求进行整合,并以批量方式发起请求的功能(前提是服务提供方提供相应的批量接口)。HystrixCollapser的封装多个请求合并发送的具体细节,开发者只需关注将业务上将单次请求合并成多次请求即可。
2) 合并请求的开销
需要注意请求合并的额外开销:用于请求合并的延迟时间窗会使得依赖服务的请求延迟增高。比如,某个请求不通过请求合并器访问的平均耗时为5ms,请求合并的延迟时间窗为lOms (默认值), 那么当该请求设置了请求合并器之后,最坏情况下(在延迟时间 窗结束时才发起请求)该请求需要15ms才能完成。
3) 合并请求的功能什么时候使用
合并请求存在额外开销,所以需要根据依赖服务调用的实际情况决定是否使用此功能,主要考虑下面两个方面:
- 请求命令本身的延迟:对于单次请求而言,如果[单次请求平均时间/时间窗口]越小,对于单次请求的性能形象越小。如果依赖服务的请求命令本身是一个高延迟的命令,那么可以使用请求合并器,因为延迟时间窗的时间消耗显得微不足道了。
- 接口请求并发量:时间窗口内并发量越大,合并求情的性能提升越明显。如果一个时间窗内只有少数几个请求,那么就不适合使用请求合并器。相反,如果一个时间窗内具有很高的并发量,那么使用请求合并器可以有效减少网络连接数量并极大提升系统吞吐量,此时延迟时间窗所增加的消耗就可以忽略不计了。
Hystrix常见配置参数
请求上下文
配置 | 配置描述 |
requestCache.enabled | 是否开启请求缓存,默认为true |
requestLog.enabled | 是否开启请求日志,默认为true |
maxRequestsInBatch | 设置批处理中允许的最大请求数 |
timerDelayInMilliseconds | 设置批处理创建到执行之间的毫秒数 |
线程池相关配置(参考参数如下)
- Threadpool Size:比如系统的 QPS 是 3000,有 100 个节点,每个节点需要承载 30 的 QPS,请求的平均处理时间是 0.2s,那么需要的线程数就是 30 * 0.2 = 6 个线程,完了要加上 0.3~0.8 的冗余,比如线程切换之类的,差不多就是 10,Hystrix 默认的线程数量是 10,就可以承担 30 的 QPS;
- Threadpool Size:系统线程池Threadpool Size 的 1.5~2 倍就可以;
配置 | 配置描述 |
coreSize | 配置线程池大小,默认为10 |
keepAliveTimeMinutes | 配置核心线程数空闲时keepAlived时长,默认1分钟 |
maxQueueSize | 配置线程池任务队列大小,默认为-1 |
maximumSize | 线程池中线程的最大数量,默认值是 10 |
queueSizeRejectionThreshold | 任务队列的请求上线,默认值是10 |
allowMaximumSizeToDivergeFromCoreSize | 是否开启最大线程数 |
execution.isolation.thread.timeoutInMilliseconds | 设置超时时间 |
execution.isolation.thread.interruptOnTimeout | 请求超时是否中断任务 |
execution.isolation.thread.interruptOnCancel | 请求取消是否终端任务 |
熔断机制相关配置
配置 | 配置描述 |
circuitBreaker.enabled | 是否开启熔断器 |
circuitBreaker.requestVolumeThreshold | 启用熔断器功能窗口时间内的最小请求数 |
circuitBreaker.sleepWindowInMilliseconds | 半熔断开启时间 |
circuitBreaker.errorThresholdPercentage | 开启熔断的失败率阈值 |
如有披露或问题欢迎留言或者入群探讨
更多推荐
所有评论(0)