天天看点

Hystrix学习总结

https://kiswo.com/article/1031

导读: 在上一篇,已经对 Hystrix 的原理进行了了解。为了加深理解和快速实际应用,编写了一些测试代码对几个关键点进行测试验证。有些结论,是官网已经说明的。另一些是通过测试或其他同行使用得出的结论。

通过测试代码 hystrix-example,可以对Hystrix的细节进行分析和验证。

1、执行Command

HystrixCommand

 的执行流程如下:

  • execute()是同步堵塞的,它调用了queue().get()方法,execute()执行完后,会创建一个新线程运行run()
  • queue()是异步非堵塞的,它调用了toObservable().toBlocking().toFuture()方法,queue()执行完后,会创建一个新线程运行run()。Future.get()是堵塞的,它等待run()运行完才返回结果
  • observe()是异步的,是热响应调用,它调用了toObservable().subscribe(subject)方法,observe()执行完后,会创建一个新线程运行run()。toBlocking().single()是堵塞的,需要等run()运行完才返回结果
  • toObservable()是异步的,是冷响应调用,该方法不会主动创建线程运行run(),只有当调用了toBlocking().single()或subscribe()时,才会去创建线程运行run()
注意:
  • 1、同一个HystrixCommand对象只能执行一次run()
  • 2、observe()中,toBlocking().single()与subscribe()是可以共存的,因为run()是在observe()中被调用的,只调用了一次
  • 3、toObservable()中,toBlocking().single()与subscribe()不可共存,因为run()是在toBlocking().single()或subscribe()中被调用的;如果同时存在toBlocking().single()和subscribe(),相当于调用了2次run(),会报错

2、回退降级

  • 继承 

    HystrixCommand

    ,重写getFallback()方法,该方法的响应要快,尽量不要有网络依赖
  • 如果有网络依赖,建议采取多次降级,即在getFallback()方法实例化 

    HystrixCommand

    ,并执行Command
  • 注意getFallback()的异常捕捉,如果getFallback(),会直接中断 

    HystrixCommand

     的流程

3、Command Name、Group、Thread-Pool

  • CommandKey/CommandName是一个依赖服务的command标识
  • GroupKey是将报告,警报,仪表板或团队/库所有权等命令组合在一起。一般可以根据服务模块或第三方客户端来分配GroupKey,一个GroupKey下可以有多个CommandKey
  • ThreadPoolKey用于监视,度量标准发布,缓存和其他此类用途的HystrixThreadPool。可以一个CommandKey绑定一个ThreadPoolKey用,这样多个线程的CommandKey就会划分到同一个ThreadPoolKey
  • 没有定义ThreadPoolKey时,ThreadPoolKey使用GroupKey,定义了ThreadPoolKey时,则使用定义值(采用线程策略隔离的情况下)
  • command在执行run()时,会创建一个线程,该线程的名称是ThreadPoolKey和序列号的组合,序列号是该线程在线程池中的创建顺序
  • 使用ThreadPoolKey的原因是多个command可能属于同一个所有权或逻辑功能『组』,但某些command又需要彼此隔离。

CommandName不设置名称的话,默认名称是从类名派生的:

getClass().getSimpleName();
           

要明确定义名称,通过HystrixCommand或HystrixObservableCommand构造函数传入:

public CommandHelloWorld(String name) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
                .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld")));
        this.name = name;
    }
           

或者为了保存每个命令分配的Setter分配,你也可以像这样缓存Setter:

private static final Setter cachedSetter = 
        Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
            .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld"));    

    public CommandHelloWorld(String name) {
        super(cachedSetter);
        this.name = name;
    }
           

4、熔断器

同时满足以下条件,熔断器将打开:

1、整个链路请求数达到阀值(

circuitBreaker.requestVolumeThreshold

),默认情况下,10秒内请求数超过20次,则符合第一个条件。

2、在满足第一个条件的前提下,如果请求的错误数比例大于阀值(

circuitBreaker.errorThresholdPercentage

),则会打开熔断器,默认为50%。

如果熔断器处于打开状态,将会进入休眠期,在休眠期内,所有请求都将被拒绝,直接执行fallback逻辑。

根据 

Metrics

 的计算,可以判断熔断器的健康状态,从而决定是否应该关闭熔断器:

  • 熔断器被打开后,根据 

    circuitBreaker.sleepWindowInMilliseconds

     设置,会休眠一段时间,这段时间内的所有请求,都直接fallback
  • 休眠时间过后,Hystrix会将熔断器状态改为半开状态,然后尝试性的执行一次command,如果成功,则关闭熔断器,如果失败,继续打开熔断器,执行新的熔断周期
  • 熔断器打开后,熔断器的健康检查指标会重置,重新开始计算

熔断器有以下几个特殊参数:

1、如果hystrix.command.default.circuitBreaker.enabled设置为false,将不使用断路器来跟踪健康状况,也不会在断路器跳闸时将其短路(即不会执行fallback)

2、如果hystrix.command.default.circuitBreaker.forceOpen设置为true,断路器将强制打开,所有请求将被拒绝,直接进入fallback

3、如果hystrix.command.default.circuitBreaker.forceClosed设置为true,断路器将强制关闭,无论错误百分比如何,都将允许请求(永远会执行run)
           

5、隔离策略

隔离策略分线程隔离和信号隔离。

5.1、线程隔离

HystrixCommand

 默认采用的是线程隔离策略。通过服务容错与保护方案 — Hystrix和测试案例的 

HystrixCommandDemo1

 可以知道,当执行 

construct()

 或 

run()

 时,会创建一个线程。因为 

Hystrix

 用到了线程池,真实的流程是这样的:

  • 1、执行 

    construct()

     或 

    run()

     时,先判断线程池中是否有空闲的线程(每个Command都可以拥有自己的线程池而不会互相影响)
  • 2、如果没有空闲的,则看当前线程数是否达到 

    hystrix.threadpool.default.coreSize

    ,如果达到,则需要排队,当队列值大于 

    hystrix.threadpool.default.maxQueueSize

    , 会拒绝请求,执行回退逻辑,如果没有达到,则创建一个新的线程来执行
  • 3、如果有空闲的,则直接从空闲的线程中取出一个来执行

当然,我们也可以设置 

hystrix.threadpool.default.maximumSize

,动态的控制线程的大小。该参数表示一个 

HystrixCommand

 可以创建的最大线程数,当线程池中的线程在 

hystrix.threadpool.default.keepAliveTimeMinutes

时间内没有使用,则会关闭一些线程,使线程数等于在 

hystrix.threadpool.default.coreSize

注意:

必须将 

hystrix.threadpool.default.allowMaximumSizeToDivergeFromCoreSize

 设置为 

true

时,

hystrix.threadpool.default.maximumSize

 才会生效

hystrix.threadpool.default.coreSize

 的默认值为10,如果需要提高此值,按照以下公式计算:

最大线程数 = QPS * 平均响应时间(单位秒)* 99% + 缓存数

举例说明:

某个接口的单台服务器QPS为10000,平均响应时间为20ms

最大线程数:10000 * 0.02 * 0.99 + 4 = 202
           

Hystrix

 官方建议尽量将最大线程数设置的小一些,因为它是减少负载并防止资源在延迟发生时被阻塞的主要工具。线程数能设置多大,有什么影响,这个需要根据自身业务情况和实际压测结果来衡量。

5.2、信号隔离

HystrixObservableCommand

 默认采用的是信号隔离。

HystrixCommand

 可以通过修改 

hystrix.command.default.execution.isolation.strategy

 参数调整为信号隔离。

  • 信号隔离是对客户端请求线程的并发限制,采用信号隔离时,hystrix的线程相关配置将无效
  • 当请求并发量大于 

    hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests

    时,请求执行fallback
  • 当fallback的并发线程数大于

    hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests

    时,fallback将抛异常fallback execution rejected

信号隔离策略下,执行 

construct()

 或 

run()

 时,使用的是应用服务的父级线程(如Tomcat容器线程)。所以,一定要设置好并发量,有网络开销的调用,不建议使用该策略,容易导致容器线程排队堵塞,从而影响整个应用服务。

6、请求合并

  • 请求合并,可以减少通信消耗和线程数的占用,提高并发
  • 请求合并会有延迟时间窗,会带来额外的开销,如果请求本身有较长的延迟,或合并的请求量较多时,请求合并会提升性能,反之,可能会降低性能

7、HystrixObservableCommand

HystrixObservableCommand

 与 

HystrixCommand

 的区别:

  • 1、它们两个是 

    Hystrix

     执行Command的两种方式
  • 2、

    HystrixCommand

     的执行封装在run(),fallback处理封装在getFallBack();

    HystrixObservableCommand

     的执行封装在contruct(),fallback处理封装在resumeWithFallback()
  • 3、

    HystrixObservableCommand

    使用的信号隔离策略,所以,使用的是应用服务的父级线程调用contruct()
  • 4、

    HystrixObservableCommand

     在contruct()中可以定义多个onNext,当调用subscribe()注册成功后,将依次执行这些onNext(),后者只能在run()中返回一个值(即一个onNext)。可以理解为 

    HystrixCommand

     一次只能发送单条数据返回,而

    HystrixObservableCommand

     一次可以发送多条数据返回
  • 5、同 

    HystrixCommand

     一样,

    HystrixObservableCommand

     使用observe(),toBlocking().single()或subscribe()可以共存,而使用toObservable(),则不能共存
参考:
  • Hystrix how to use
  • Hystrix Configuration
  • Hystrix examples
  • Hystrix常用功能介绍
  • Hystrix使用入门手册