Spring Cloud Hystrix

概述

在微服务架构中,将系统拆分为很多服务单元,各个单元的应用通过服务的注册与订阅的方式互相依赖。由于每个单元都在不同的进程中进行,依赖通过远程调用的方式执行,这样就可能因为网络原因或是依赖服务自身问题出现调用故障或延迟,而这些问题会直接导致调用方的对外服务也出现延迟,若此时调用方的请求不断增加,最后出现挤压,导致服务自身瘫痪。 针对上述的问题,Spring Cloud Hystrix实现了断路器、线程隔离等一些列服务保护功能。目标在于:通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更加强大的容错能力。Hytrix具备服务降级、服务熔断、线程和信号隔离、请求缓存、请求合并以及服务监控等强大的功能。


1.原理分析

工作流程: 工作流程图

  • 1.创建HystrixCommand或HystrixObservableCommand对象 首先创建上面的两个对象之一来表示依赖服务的操作请求,同时传递所有需要的参数。采用”命令模式”来实现对服务调用操作的封装。
    • HystrixCommand:用于依赖的服务返回单个操作的时候
    • HystrixObservableCommand:用在依赖的服务返回多个操作结果的时候
  • 2.命令执行 Hystrix在执行时会根据创建的Command对象以及具体的情况来选择一个执行。其中HystrixCommand实现俩个执行方式.
    • execute():同步执行,从依赖的服务返回一个单一的结果对象,或是在发生错误的时候抛出异常
    • queue():异步执行,直接返回一个Future对象.其中包括了服务执行结束时要返回单一结果对象

HystrixObservableCommand实现了另外的方式。

  • observe():返回Observable对象,它代表了操作的多个结果。
  • toObservable():同样返回Observable对象,也代表了操作的多个结果。

RxJava响应式编程模式。重要的组成部分是:”事件源”,”订阅者”

  • 3.结果是否被缓存 若当前命令的请求缓存功能是被启用的,并且该命令缓存命中,那么缓存的结果会立即返回。
  • 4.断路器是否打开 在命令结果没有缓存命中的时候,Hystrix在执行命令前需要检查断路器是否打开:
    • 如果断路器是打开的,那么Hystrix在执行命令,而是转接到fallback处理逻辑(对应下面第八步)
    • 如果断路器是关闭的,那么Hystrix挑到第五步,检查是否有可用资源来执行命令。
  • 5.线程池/请求队列/信号量是否沾满 如果与命令相关的线程池和请求队列,或者是信号量已经被沾满,那么Hystrix也不会执行命令,而是转接到fallback处理逻辑(对应下面第八步). 这里Hystrix所判断的线程池并非容器的线程池,而是每个依赖服务的专有线程池。Hystrix为了保证不会因为某个依赖服务的问题影响到了其他的依赖服务而采用了仓壁模式来隔离每个依赖的服务
  • 6.HystrixObservableCommand.construct()或HystrixCommnad.run() Hystrix会根据我们编写的方法来决定采取怎样的方式去请求依赖服务
    • HystrixCommand.run():返回一个单一的结果,或者抛出异常。
    • HystrixObservableCommand.construct():返回一个Observable对象来发射多个结果,或通过onError()发送错误通知

如果run()或construct()方法的执行时间超过了命令设置的超时阀值,当前处理线程将会抛出一个TimeoutException。这样会直接跳转到(第八步)

  • 7.计算断路器的健康度 Hystrix会将成功、失败、拒绝、超时等信息报告给断路器,而断路器会维护一组计数器来统计这些数据。断路器会使用这些统计数据决定是否偶要将断路器打开,来对某个依赖服务的请求进行”熔断/短路”,直接恢复期结束。若在恢复期结束后,根据统计数据判断如果还是未达到健康指标,就再次”熔断/短路”
  • 8.fallback处理 当命令执行失败的时候,hystrix会进入fallback尝试回退处理,称为”服务降级”。引起服务降级处理的情况下有几种:
    • 第四步,当前命令处于”熔断/短路”状态,断路器是打开的时候。
    • 第五步,当前命令的线程池、请求队列或者信号量被沾满的时候。
    • 第六步,HystrixObservableCommand.construct()或者HystrixCommand.run()抛出异常的时候。

在降级逻辑中,我们需要实现一个通用的响应结果,并且该结果处理逻辑应当是从缓存或是根据一些静态逻辑来获取。如果一定要在降级逻辑中包含网络请求,那么该请求也必须被包装在HystrixCommand或是HystrixObservableCommand中,从而形成级联降级策略,而最终的降级逻辑一定不是一个网络请求的处理,而一个能够稳定地返回结果的处理逻辑。

  • 9.返回成功的响应 当Hystrix命令执行成功之后,它会将处理结果返回或是以Observable的形式返回。

3.断路器原理

断路器在HystrixCommand和HystrixObservaleCommand执行过程中有很重要的作用,它就是Hystrix的核心部件。 HystrixCircuitBreaker核心的原理实现。查看源码,深入了解。


4.依赖隔离

“舱壁模式”。在Hystrix使用线程池的隔离,它会为依赖的每一个服务创建一个独立的线程池,这样就算某个依赖出现延迟过高的情况,也只是对该依赖服务的调用产生影响,而不会拖慢其他依赖的服务。通过实现对依赖服务的线程池隔离。可以带来如下优势:、

  • 应用自身得到保护,不会受不可控的依赖服务影响。即便给依赖服务分配的线程池填满,也不会影响应用其余部分。
  • 可以有效降低接入新服务的风险。如果新服务接入后运行不稳定或存在问题。完全不会影响其他的请求。
  • 当依赖的服务从失效恢复正常后,它的线程池会被清理并且能够马上恢复健康的服务,相比之下,容器级别的清理恢复速度要慢得多。
  • 当依赖的服务出现噢诶之错误的时候,线程池会快速反应问题。同时也可以在不影响应用功能的情况通过实时的动态属性刷新处理。
  • 当依赖的服务因机制调整等原因造成其性能出现很大的变化的时候,线程池的监控指标信息会反应出这样的变化。同时我们也可以通过实时动态刷新自身应用对依赖服务的阀值进行调整以适应依赖方的改变。

在Hystrix中除了可以使用线程池之外,还可以使用信号量来控制单个依赖服务的并发哦控制,信号量的开销远比线程池的开销小,但是它不能设置超时和实现异步访问。所以在依赖服务足够可靠的情况下,可以使用型号量。

  • 命令执行:如果将隔离策略参数execution.isolation.strategy设置为SEMAPHORE,Hystrix会使用信号量代替线程池来控制依赖服务的并发。
  • 降级逻辑:当Hystrix尝试降级逻辑时,它会调用线程中使用型号量。

5.定义服务降级

fallback是Hystrix命令执行失败的时候使用的后备方法,用于实现服务的降级处理逻辑。在HystrixCommand中可以通过重载getFallBack(()方法来实现服务降级逻辑,Hystrix会在run()执行过程中出现错误、超时、线程池拒绝、断路器熔断等情况时,执行getFallback()方法内的逻辑。 在实际使用时,大多数执行过程中可能会失败的Hystrix命令实现服务降级逻辑,但是也有一些情况可以不去实现降级逻辑。

  • 执行写操作命令:当Hystrix命令是用来执行写操作而不是返回一些信息的时候,通常情况下这类型的操作的返回类型是void或者是空的Observable,实现服务降级的意义不大。当写入操作失败的时候,我们通常只需要通知调用者即可。
  • 执行批处理或离线计算的命令:当Hystrix命令是用来执行批量处理程序生成一份报告或是进行任何类型的离线计算时,那么通常这些操作只需要将错误传播给调用者,然后让调用者稍后重试而不是发送给调用者一个静默的降级处理响应。

6.异常处理
  • 异常传播 在HystrixCommand实现的run()方法中抛出异常时,除了HystrixBadRequestException之外,其他异常均会被Hystrix认为命令执行失败并触发服务降级的处理逻辑,所以当需要在命令执行中抛出不触发服务降级的异常时来使用它。 在使用注解配置实现Hystrix命令时,它还支持忽略指定异常类型功能,只需要通过@HystrixCommand注解的ignoraExceptions参数。
  • 异常获取 当Hystrix命令因为异常进入服务降级逻辑之后,需要对不同异常做针对性的处理,那么我们如何来获取当前抛出的异常呢? 在传统继承方式实现的Hystrix命令中,我们可以用getFallback()方法通过Throwable getExecutionException()方法来获取具体的异常,通过来判断进入不同的处理逻辑。

7.命令名称、分组以及线程池划分

以继承方式实现的Hystrix命令使用类名作为默认的命令名称,我们也可以在构造函数中通过Setter静态类来设置,比如:

    Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GroupName").andCommandKey(HystrixCommandKey.Factory.asKey("CommandName")))

通过设置命令组,Hystrix会根据组织和统计命令的告警、仪表盘等信息。Hystrix命令默认的线程划分也是根据命令组来直线的。默认情况下,Hystrix会让相同组的命令使用同一个线程池,我们需要在创建Hytrix命令时为其它指定命令组来实现默认的线程池划分。

Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("CommandGroupKey")).andCommandKey(HystrixCommandKey.Factory.asKey("CommandKey")).andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ThreadPoolKey"))

8.请求缓存

当系统用户不断增加时,每个微服务需要承受的并发压力越来越大。在分布式环境下,通常压力来自于对依赖服务的调用,因为请求依赖服务的资源需要通过通信来实现,这样的依赖方式比起进程内的调用方式会引起一部分的性能损失,同时HTTP相比其他的高性能的通信协议在速度上没有任何的优势。Hystrix中提供了请求缓存的功能,方便开启和使用请求缓存来优化系统,达到减轻高并发时的请求线程消耗、降低请求响应时的效果。

  • 开启请求缓存功能: 重载该方法可以开启请求缓存 getCacheKey()//开启请求缓存 好处:
    • 减少重复的请求数,降低依赖服务的并发读
    • 在同一用户请求的上下文中,相同依赖服务的返回数据始终保持一致。
    • 请求缓存在run()和construct()执行之前生效,所以可以有效减少不必要的线程开销。
  • 清理失效缓存功能 使用请求缓存时,如果只是读操作,那么不需要考虑内存是否正确的问题,但是如果请求命令中还有更新数据的写操作,那么缓存中的数据就需要我们在进行写操作时进行及时处理,以防止读操作的请求命令获取到了失效的数据。

9.其余的命令
  • 请求缓存
  • 请求合并
  • 属性详解
  • command属性
  • collapser属性
  • threadPoll属性

10.Hystrix仪表盘

11.Turbine集群监控

12.构建监控聚合服务

13.消息代理结合