Hystrix学习总结

 2018-05-04 15:03:05     Hystrix  Netflix  开源框架   254


导读: 在上一篇,已经对 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


HystrixObservableCommandHystrixCommand 的区别:

  • 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(),则不能共存


参考: