hystrix原理应用

简介

在分布式架构中,一个应用依赖多个服务是非常常见的,如果其中一个依赖由于延迟过高发生阻塞,调用该依赖服务的线程就会阻塞,如果相关业务的QPS较高,就可能产生大量阻塞,从而导致该应用/服务由于服务器资源被耗尽而拖垮。

另外,故障也会在应用之间传递,如果故障服务的上游依赖较多,可能会引起服务的雪崩效应。就跟数据瘫痪,会引起依赖该数据库的应用瘫痪是一个道理。

hystrix 是 netflix 开发的在 SOA/微服务架构 中提供服务隔离、熔断、降级机,制的工具/框架,用于保障微服务架构中的高可用。

hystrix 设计的目标与原则

设计目标:

  1. 对来自依赖的延迟和故障进行防护和控制——这些依赖通常都是通过网络访问的。
  2. 阻止故障的连锁反应。
  3. 快速失败并迅速恢复。
  4. 回退并优雅降级。
  5. 提供近实时的监控与告警

设计原则:

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

hystrix 防止雪崩的实现方式

  1. 使用命令模式将所有对外部服务(或依赖关系)的调用包装在HystrixCommand或HystrixObservableCommand对象中,并将该对象放在单独的线程中执行;
  2. 每个依赖都维护着一个线程池(或信号量),线程池被耗尽则拒绝请求(而不是让请求排队)。即线程隔离。
  3. 记录请求成功,失败,超时和线程拒绝。
  4. 服务错误百分比超过了阈值,熔断器开关自动打开,一段时间内停止对该服务的所有请求。
  5. 请求失败,被拒绝,超时或熔断时执行降级逻辑。
  6. 近实时地监控指标和配置的修改。

hystrix 运行机制

工作流程

hystrix 工作流程流程:

  1. 创建 HystrixCommand 或者 HystrixObservableCommand 对象

  2. 执行命令execute()、queue()、observe()、toObservable()

    • execute: 阻塞方法, 从依赖请求中接收单个响应或出错抛出异常
    • queue: 从依赖请求中返回一个包含单个响应的 Future 对象
    • observe: 订阅一个从依赖请求中返回的代表响应的 Observable 对象
    • toObservalbe: 返回一个 Observable 对象,只有当你订阅它时,它才会执行Hystrix命令并发射响应
    • 需要注意的是: 同步调用方法execute()实际上就是调用queue().get()queue()调用的是toObservable().toBlocking().toFuture().也就是说,最终每一个 HystrixCommand 都是通过 Observable 来实现的,即使这些命令仅仅是返回一个简单的值
  3. 如果请求结果缓存这个特性被启用,并且缓存命中,则缓存的回应会立即通过一个 Observable 对象的形式返回

  4. 检查回路是否打开,如果被打开(tripped),Hystrix 将不会执行这个命令,而是直接执行 getFallback

  5. 如果线程池、信号量、队列已满,Hystrix 将不会执行这个命令,而是直接执行 getFallback

  6. 执行 HystrixCommand.run()HystrixObservableCommand.construct(),如果这两个方法执行超时或者执行失败,则执行getFallback()

    • HystrixCommand.run() 返回单个响应或者抛出异常
    • HystrixObservableCommand.construct() 返回一个发射响应的Observable或者发送一个 onError 的通知
    • 如果执行run()方法或者construct方法的执行时间大于命令所设置的超时时间值,那么该线程将会抛出一个TimeoutException异并执行 fallback,并且如果run()或者construct()方法没有被取消或者中断,会丢弃这两个方法最终返回的结果。(注意:没有任何方式可以强制终止一个潜在线程[latent thread]的运行,Hystrix能够做的最好的方式是让JVM抛出一个InterruptedException异常.如果你的任务被Hystrix所包装,并不意味着会抛出一个InterruptedExceptions异常,该线程在Hystrix的线程池内会进行执行,虽然在客户端已经接收到了TimeoutException异常.)
  7. Hystrix 会将请求成功,失败,被拒绝或超时信息报告给熔断器,熔断器维护一些用于统计数据用的计数器。

    • Hystrix会报告成功、失败、拒绝和超时的指标给回路器,回路器包含了一系列的滑动窗口数据,并通过该数据进行统计。
    • 它使用这些统计数据来决定回路器是否应该熔断,如果需要熔断,将在一定的时间内不在请求依赖[短路请求](这个一定的时间可以通过配置指定),当再一次检查请求的健康的话会重新关闭回路器。

几个组件的描述

熔断器(HystrixCircuitBreaker)

HystrixCircuitBreaker工作流程:

  1. 调用 allowRequest() 判断是否允许将请求提交到线程池
    1. 如果熔断器强制打开(circuitBreaker.forceOpen 为 true), 不允许放行, 返回
    2. 如果熔断器强制关闭(circuitBreaker.forceClosed 为 true),允许放行。这种情况下不必关注熔断器实际状态,也就是说熔断器仍然会维护统计数据和开关状态,只是不生效
  2. 调用 isOpen() 判断熔断器开关是否打开
    1. 如果开关打开, 则进入第三步, 否则继续流程
    2. 如果一个周期内总请求数小于 circuitBreaker.requestVolumeThreshold, 允许请求放行, 否则继续判断
    3. 如果一个周期错误率小于 circuitBreaker.errorThresholdPercentage, 允许放行。否则打开熔断器开关,进入第三步
  3. 调用 allowSingleTest() 判断是否允许单个请求通行, 检查依赖服务是否恢复
    1)如果熔断器打开, 且距离熔断器打开的时间或上一次试探请求放行的时间超过circuitBreaker.sleepWindowInMilliseconds的值时,熔断器器进入半开状态,允许放行一个试探请求;否则,不允许放行
    2)为了提供决策依据,每个熔断器默认维护了10个bucket,每秒一个bucket,当新的bucket被创建时,最旧的bucket会被抛弃。其中每个blucket维护了请求成功、失败、超时、拒绝的计数器,Hystrix负责收集并统计这些计数器
    3) 服务的健康状况 = 请求失败数 / 请求总数, 熔断器开关由关闭到打开的状态转换是通过当前服务健康状况和设定阈值比较决定的。熔断器的开关能保证服务调用者在调用异常服务时,快速返回结果,避免大量的同步等待。并且熔断器能在一段时间后继续侦测请求执行结果, 提供恢复服务调用的可能

熔断器开关转换过程图:

资源隔离

Hystrix通过将每个依赖服务分配独立的线程池进行资源隔离, 从而避免服务雪崩。

客户端(第三方包,网络调用等)会在单独的线程里面执行(把逻辑包装成 Command), 会与调用该任务的线程进行隔离, 以此防止调用者依赖的服务消耗时间

通过单独线程池隔离隔离调用的优点:

  1. 不受失控的客户端的影响, 即使某一个依赖的线程池满了也不会影响其他依赖的调用
  2. 降低了接收客户端数据的风险,如果发生问题,它会与出问题的客户端所隔离, 不会影响其他依赖的内容
  3. 如果一个客户端库的配置错误,线程池可以很快的感知这一错误(通过增加错误比例,延迟,超时,拒绝等),并可以在不影响应用程序的功能情况下来处理这些问题(可以通过动态配置来进行实时的改变)
  4. 如果一个客户端服务的性能变差,可以通过改变线程池的指标(错误、延迟、超时、拒绝)来进行属性的调整,并且这些调整可以不影响其他的客户端请求

线程池的缺点:

  1. 增加了CPU的开销,每个命令都在单独的线程池上执行,这种涉及到命令的排队、调度和上下文切换
  2. 一般情况下线程池的引入开销小,不会造成重大的性能影响。但是访问一些延迟比较低的服务(如只依赖内存的服务),引入线程池就开销比较大了。这个时候可以考虑更加轻量级的实现,如信号量隔离

参考资料


hystrix原理应用
https://haobin.work/2020/06/07/中间件/hystrix原理应用/
作者
Leo Hao
发布于
2020年6月7日
许可协议