Spring Cloud Hystrix 服务熔断、降级原理

在分布式系统架构中,如果一个应用不能对来自依赖的故障进行隔离,那该应用本身就处在被拖垮的风险中。 因此,为了构建稳定、可靠的分布式系统,我们的服务应当具有自我保护能力,当依赖服务不可用时,当前服务启动自我保护功能,从而避免发生雪崩效应。在分布式微服务系统设计时,要使用一定的降级策略,来保证当服务提供方不可用时,服务调用方可以切换到降级后的策略进行处理。Hystrix 作为熔断器组件,其使用范围还是很广泛的。本文简单介绍下 Hystrix 的原理。

一、Hystrix 是什么?

Hystrix 是 Netflix 的一款开源的容错框架,通过服务隔离来避免由于依赖延迟、异常,引起资源耗尽导致系统不可用的解决方案。

二、Hystrix 可以做什么?

Hystrix 被设计用来做了下面几件事:

  1. 保护系统间的调用延时以及错误,特别是通过第三方工具的网络调用
  2. 阻止错误在分布式系统之前的传播
  3. 快速失败和迅速恢复
  4. 错误回退和优雅的服务降级

三、Hystrix 解决了什么问题?

  1. 限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用,通过线程池隔离和信号量隔离实现。
  2. Hystrix 提供了优雅降级机制:超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回兜底数据。
  3. Hystrix 提供了熔断器实现,当失败率达到阀值自动触发降级(如因网络故障/超时造成的失败率高),熔断器触发的快速失败会进行快速恢复等。

四、Hystrix遵循的设计原则

  1. 防止任何单独的依赖耗尽资源(线程)
  2. 过载立即切断并快速失败,防止排队
  3. 尽可能提供回退以保护用户免受故障
  4. 使用隔离技术(例如隔板,泳道和断路器模式)来限制任何一个依赖的影响
  5. 通过近实时的指标,监控和告警,确保故障被及时发现
  6. 通过动态修改配置属性,确保故障及时恢复
  7. 防止整个依赖客户端执行失败,而不仅仅是网络通信

五、Hystrix 的工作流程

下图描述了使用 Hystrix 的一次请求调用服务层的流程。【图片来源于:Hystrix 官方文档点击看大图
在这里插入图片描述
Hystrix 整个工作流如下:

  1. 构造一个 HystrixCommand 或 HystrixObservableCommand 对象,用于封装请求,并在构造方法配置请求被执行需要的参数;
  2. 执行命令,Hystrix提供了多种执行命令的方法(详见本文第六章);
  3. 判断是否使用缓存响应请求,若启用了缓存,且缓存可用,直接使用缓存响应请求。Hystrix 支持请求缓存,但需要用户自定义启动;
  4. 判断熔断器是否打开(熔断器有3种状态,详见第七章),如果打开,跳到第8步;
  5. 判断线程池/队列/信号量是否已满,已满则跳到第8步;
  6. 执行 HystrixObservableCommand.construct() 或 HystrixCommand.run(),如果执行失败或者超时,跳到第8步;否则,跳到第9步;
  7. 统计熔断器监控指标;
  8. 走Fallback备用逻辑;
  9. 返回请求响应。
    从流程图上可知道,第5步线程池/队列/信号量已满时,还会执行第7步逻辑,更新熔断器统计信息,而第6步无论成功与否,都会更新熔断器统计信息。

六、Hystrix 执行命令的几种方法

Hystrix 提供了4种执行命令的方法,execute() 和 queue() 适用于 HystrixCommand 对象,而 observe() 和toObservable() 适用于 HystrixObservableCommand 对象。

execute()
以同步堵塞方式执行 run(),只支持接收一个值对象。Hystrix 会从线程池中取一个线程来执行 run(),并等待返回值。

queue()
以异步非阻塞方式执行 run(),只支持接收一个值对象。调用 queue() 就直接返回一个 Future 对象。可通过 Future.get() 拿到 run() 的返回结果,但 Future.get() 是阻塞执行的。若执行成功,Future.get() 返回单个值。当执行失败时,如果没有重写 fallback,Future.get() 抛出异常。

observe()
事件注册前执行 run() / construct(),支持接收多个值对象,取决于发射源。调用 observe() 会返回一个 hot Observable,也就是说,调用 observe() 自动触发执行 run() / construct(),无论是否存在订阅者。

如果继承的是 HystrixCommand,Hystrix 会从线程池中取一个线程以非阻塞方式执行 run();如果继承的是HystrixObservableCommand,将以调用线程阻塞执行 construct()。

observe() 使用方法:

  1. 调用 observe() 会返回一个 Observable 对象;
  2. 调用这个 Observable 对象的 subscribe() 方法完成事件注册,从而获取结果。

toObservable()
事件注册后执行 run() / construct(),支持接收多个值对象,取决于发射源。调用 toObservable() 会返回一个 cold Observable,也就是说,调用 toObservable() 不会立即触发执行 run() / construct(),必须有订阅者订阅 Observable 时才会执行。

如果继承的是 HystrixCommand,Hystrix 会从线程池中取一个线程以非阻塞方式执行 run(),调用线程不必等待run();如果继承的是 HystrixObservableCommand,将以调用线程堵塞执行 construct(),调用线程需等待construct() 执行完才能继续往下走。

toObservable() 使用方法:

  1. 调用 observe() 会返回一个Observable对象;
  2. 调用这个 Observable 对象的 subscribe() 方法完成事件注册,从而获取结果。

需注意的是,HystrixCommand 也支持 toObservable() 和 observe(),但是即使将 HystrixCommand 转换成Observable,它也只能发射一个值对象。只有 HystrixObservableCommand 才支持发射多个值对象。

七、熔断器的三种状态

Hystrix 提供的熔断器具有自我反馈,自我恢复的功能,Hystrix会根据调用接口的情况,让熔断器在closed,open,half-open 三种状态之间自动切换。

  • open 状态:说明打开熔断,也就是服务调用方执行本地降级策略,不进行远程调用。
  • closed 状态:说明关闭了熔断,这时候服务调用方直接发起远程调用。
  • half-open 状态:是一个中间状态,当熔断器处于这种状态时,直接发起远程调用。

三种状态的转换:

closed → open:正常情况下熔断器为 closed 状态,当访问同一个接口次数超过设定阈值并且错误比例超过设置错误阈值的时候,就会打开熔断机制,这时候熔断器状态从 closed → open。

open → half-open:当服务接口对应的熔断器状态为 open 状态时候,所有服务调用方调用该服务方法时候都是执行本地降级方法,那么什么时候才会恢复到远程调用呢?Hystrix 提供了一种测试策略,也就是设置了一个时间窗口,从熔断器状态变为 open 状态开始的一个时间窗口内,调用该服务接口时候都委托服务降级方法进行执行。如果时间超过了时间窗口,则把熔断状态从 open → half-open,这时候服务调用方调用服务接口时,就可以发起远程调用而不再使用本地降级接口,如果发起远程调用还是失败,则重新设置熔断器状态为open状态,从新记录时间窗口开始时间。

half-open → closed:当熔断器状态为 half-open,这时候服务调用方调用服务接口时候,就可以发起远程调用而不再使用本地降级接口,如果发起远程调用成功,则重新设置熔断器状态为 closed 状态。

用来判断熔断器从 closed → open 转换的数据是 HystrixCommandMetrics 对象来做的,该对象用来存HystrixCommand 的一些指标数据,比如接口调用次数,调用接口失败的次数等等。

八、Hystrix 几种容错方案

Hystrix 的容错主要有以下几种方案:

  • 隔离模式(线程池隔离、信号量隔离)
    对不同类型的请求使用线程池来资源隔离,每种类型的请求互不影响,如果一种类型的请求线程资源耗尽,则对后续的该类型请求直接返回,不再调用后续资源。
  • 熔断模式
    如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不再继续调用目标服务,而是调用降级服务返回数据,快速释放资源。如果目标服务情况好转则恢复调用。
  • 限流模式
    上述的熔断模式和隔离模式都属于出错后的容错处理机制,而限流模式则可以称为预防模式。限流模式主要是提前对各个类型的请求设置最高的QPS阈值,若高于设置的阈值则对该请求直接返回,不再调用后续资源。这种模式不能解决服务依赖的问题,只能解决系统整体资源分配问题,因为没有被限流的请求依然有可能造成雪崩效应。

隔离模式一般使用两种:

  • 线程池隔离模式
    使用一个线程池来存储当前的请求,线程池对请求作处理,设置任务返回处理超时时间,堆积的请求堆积入线程池队列。这种方式需要为每个依赖的服务申请线程池,有一定的资源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队列慢慢处理)。
  • 信号量隔离模式
    使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,请求来了的话,先判断计数器的数值,若超过设置的最大数值则丢弃该类型的新请求,若不超过则执行计数操作请求,使计数器 +1,请求返回则计数器 -1。这种方式是严格的控制线程且立即返回模式,无法应对突发流量(流量洪峰来临时,处理的线程超过数量,其他的请求会直接返回,不继续去请求依赖的服务)。

具体 Hystrix 的工程实战,见 Hystrix 工程实战

【本文在写作时,参考了以下几篇文档】

  1. Hystrix 官方文档
  2. Hystrix原理与实战
  3. Hystrix熔断机制原理剖析
  4. Hystrix是什么
发布了32 篇原创文章 · 获赞 11 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/piaoranyuji/article/details/103700542