Hystrix 工作流程解析

Hystrix内部存在一套精细、高效和简洁的流程,该流程为实现hystrix的服务降级、故障隔离、访问限流和对被依赖服务运行信息的维护奠定了坚实的基础。

Hystrix整体工作流程

hystrix整个工作流程涉及到熔断器、缓冲、线程池等组件。这些组件分别实现了故障隔离、访问合并、服务降级和访问限流等功能,正是这些组件高效密切的配合确保了服务的稳定运行。

下面是hystrix的整体工作流程图:

Hystrix 工作流程解析_第1张图片

整个流程可以大致归纳为如下几个步骤:

  1. 创建HystrixCommand或则HystrixObservableCommand对象
  2. 调用HystrixCommand或则HystrixObservableCommand方法执行Command
  3. 根据依赖调用的结果缓存情况进行相应的处理
  4. 根据该类依赖请求熔断器的打开状态进行相应的处理
  5. 根据该类依赖请求的总量进行相应的处理
  6. 执行对外部依赖的请求逻辑
  7. 计算统计熔断器数据值
  8. 调用降级方法或则返回依赖请求的真正结果

实例化Command对象

HytrixCommand和HystrixObservableCommand包装了对外部依赖访问的逻辑,整个流程的第一个步骤就是实例化HystrixCommand或则HystrixObservableCommand对象。在构造这两个Command对象时,可以通过构造方法传递任何执行过程中需要的参数。

如果对外部依赖调用只返回一个结果值,那么可以实例化一个HystrixCommand对象

HystrixCommand command = new HystrixCommand(arg1, arg2);

如果在调用外部依赖时需要返回多个结果值时,可以实例化一个HystrixObservableCommand对象

HystrixObservableCommand command = new HystrixObservableCommand(arg1, arg2);

调用Command方法触发流程

Hystrix API提供了四个触发流程的方法供开发者调用,下面四个方法中前两个方法仅适用于HystrixCommand类。

  1. execute()方法,调用外部依赖只返回一个值,该方法会阻塞调用线程
  2. queue()方法,调用外部依赖只返回一个值,返回一个Future对象
  3. observe()方法,返回一个hot observable,调用该方法时直接执行command逻辑。
  4. toObservable()方法,返回一个cold observable,在subscribe返回的Observable时真正执行Command逻辑

其中execute()方法和queue()方法是HystrixCommand类特有的,适用于外部依赖只返回一个结果的情况下。execute()方法的实现是通过调用HystrixCommand的queue()方法获得Future,调用Future的get()方法获取最终的返回值。而无论是HystrixCommand的execute()方法还是queue()方法,最终都是通过调用toObservable()方法来实现的。只不过是HystrixCommand这两个方法返回的结果只有一个值。


外部依赖返回结果缓存处理

如果开启了Hystrix请求结果缓存开关并且缓存了历史调用返回的结果,那么hystrix会直接将缓存的结果返回。在使用HystrixCommand和HystrixObservableCommand类封装依赖请求逻辑时,可以通过重载getCacheKey()实现结果的缓存。通过实现同类请求结果的缓存,可以在同一个请求Context中有效降低对外部依赖的实际调用次数。

以下是在两个线程中对外部依赖http请求的流程示意图:

Hystrix 工作流程解析_第2张图片

结果缓存的好处:

  • 减少线程重复调用外部依赖次数
  • 在同一个请求Context中保持外部依赖返回结果的一致性

熔断器状态

如果流程中判断到依赖调用的结果并没有缓存,那么接下来的流程需要判断熔断器的开启状态。在熔断器开启的情况下,整个流程将转入调用次数控制环节,在熔断器关闭的情况下将直接调用服务降级方法返回默认结果。HystrixCircuitBreaker是Hystrix中实现熔断器功能的核心类,HystrixCommand和HystrixObservableCommand通过与HystrixCircuitBreaker交互实现了熔断器功能的开启和关闭。

下面是HystrixCircuitBreaker和Command的交互流程和内部开关控制逻辑:
Hystrix 工作流程解析_第3张图片

熔断器开关控制条件:

  • 对外部依赖调用的次数满足配置的阈值
  • 对外部依赖调用发生错误的比率满足配置的阈值

在满足以上两个条件时,熔断器打开熔断开关,之后所有对外部依赖调用都将被直接断开。在开关打开时长超过试探窗口期后,熔断器将尝试放行部分外部依赖的调用,根据试探的结果决定重新开启或则关闭熔断开关。


外部依赖调用次数控制

Hystrix引入了ThreadPool和semaphore两种方式实现对同一类外部依赖调用次数的控制。如果对同一类外部依赖调用的塞满ThreadPool的队列或则超过semaphore配置的阈值,接下来的依赖调用将直接断开,通过调用降级方法返回默认值。ThreadPool和semaphore都可以实现限流功能,相对于semaphore方式,ThreadPool还实现了故障的隔离。

Hystrix的ThreadPool

Hystrix为每一类外部依赖调用提供不同的ThreadPool,每一类外部依赖调用都在独立的线程中执行,任何一类依赖调用出现问题也不会影响到其他依赖调用的正常执行。通过为每次外部依赖调用分配独立的线程执行,使得外部依赖服务的故障、调用超时都不会阻塞调用线程从而有效的隔离了故障的影响。

ThreadPool应用场景

基于以下原因我们可以考虑采用ThreadPool隔离故障:

  • 系统中存在大量被依赖服务,并且这些服务由不同项目组开发维护,这些服务的稳定行和效率差异较大
  • 外部依赖可能会提供客户端SDK,并且客户端SDK经常变动或则更新
  • 客户端SDK可能包含网络请求、超时重试、缓存获取保存(本地内存或则网络缓存)和其他耗时不稳定的逻辑代码
  • 依赖的服务经常变动并且性能随时可能发生变化

调用外部依赖故障隔离示意图:
Hystrix 工作流程解析_第4张图片

ThreadPool机制的优点

  • 服务从外部依赖调用故障中隔离开来,外部依赖故障只会消耗掉分配给该类调用的线程池资源而不会拖垮服务本身
  • 服务可以安全、方便的集成外部依赖提供的SDK代码,在发生故障时将故障隔离在SDK内避免了对服务本身造成影响
  • 在外部依赖恢复时,可以迅速清理ThreadPool中积压的任务,达到快速恢复调用的能力
  • 外部依赖提供的SDK配置错误或则服务故障可以快速的通过调用失败、成功、超时这些统计指标暴露出来,为排查和恢复故障提供及时的预报

ThreadPool机制的缺点

由于线程池的引入,对外部依赖的调用增加了任务排队、任务调度和上下文切换的开销。这些开销随着外部依赖调用流量的增长会变得更大,所以需要根据外部依赖的业务特点决定是否采用 ThreadPool机制。

下面是Netflix耗时测试结果:

Hystrix 工作流程解析_第5张图片

Hystrix的Semaphore

使用Semaphore可以控制对外部依赖并发调用的数量,Semaphore省去了任务排队、任务调度和执行线程上下文切换的开销。但是采用Semaphore机制后,调用外部依赖将在请求线程中执行,这样外部依赖调用的延时、故障都将影响服务本身。通常在外部依赖耗时小、可靠性较高的情况下选择 Semaphore机制,规避采用ThreadPool机制带来明显的耗时负担。

HystrixCommand和HystrixObservableCommand在两处地方使用了Semaphore机制:

  • 服务降级:服务降级方法始终在请求线程中执行并采用Semaphore控制请求并发量
  • Command执行:可以通过设置Command的执行策略为Semaphore,那么在执行外部依赖请求时将采用Semaphore控制并发流量

外部依赖调用逻辑执行

HystrixCommand和HystrixObservableCommand封装了外部依赖调用。通过重写HystrixCommand的run()方法和HystrixObservableCommand类的construct()方法实现外部依赖的调用。

  • HystrixCommand的run()方法只返回一个请求结果
  • HystrixObservableCommand的construct()方法返回一个Observable对象,通过订阅该Observerable可以获取多个返回结果

如果run()方法或则construct()方法在执行外部依赖请求出现超时,那么请求线程或则调用线程将抛出TimeOutException。Hystrix接下来直接返回调用降级方法获得的值而不管超时后外部依赖实际返回结果。除了通过抛出一个InterruptException外没有更好的方式中断外部调用线程的执行,所以建议调用外部依赖逻辑添加对InterruptException的处理以便在必要时中断请求线程的执行,而目前很多Http Client的实现并没有很好的支持该中断的相应,所以在使用Http Client发送http请求时需要合理设置超时时间以免长时间阻塞执行线程。

如果外部依赖调用正常返回,那么Hystrix metrics将进行必要的数据统计,这些统计数据关系到HystrixCircuitBreaker的正确运转。


熔断器数据统计

HystrixCircuitBreaker通过维护一系列的counter记录外部依赖请求的执行情况。在外部依赖请求超时、失败、成功时,这些信息都将被记录到熔断器内。熔断器根据维护的这些信息,在符合触发条件下开启熔断功能,在条件合适的时候关闭熔断开关。

服务降级和返回调用值

外部依赖请求成功时返回

外部依赖调用成功时将通过Observable对象返回结果值,鉴于构造Command对象的不同和调用执行方法的不同,结果值可能经过一些列的转化过程,不过从本质上讲都是通过Observable对象返回结果值。

外部依赖结果值返回流程图:

Hystrix 工作流程解析_第6张图片

外部依赖请求失败触发服务降级

Hystrix在调用外部依赖出现异常、调用超时、熔断器开关打开、并发调用流量达到ThreadPool(Semaphore)的阈值时触发服务降级。HystrixCommand通过重载getFallback()方法或则HystrixObservableCommand的resumeWithFallback()方法实现服务的降级。通过getFallback()方法只能返回一个结果值,而通过resumeWithFallback()方法返回的Observable对象可以给请求线程返回多个结果值。

一般降级方法最好不要有网络请求这类耗时不稳定的操作,如果必须实现这类操作建议对降级逻辑实行同样的隔离控制策略。

你可能感兴趣的:(SpringCloud)