此文为《Spring Cloud、Nginx高并发核心编程_尼恩 (作者) _机械工业出版社》 关于Hystix部分的读书笔记,不太清楚的地方又在网上另外找了资料 

一、简介

RPC保护目标

在分布式多节点集群架构系统内部,在节点之间进行RPC保护的目标如下:

1.避免整个系统出现级联失败而雪崩,这是非常重要的目标。

在RPC调用过程中,需要防止由单个服务的故障而耗尽整个服务集群的线程资源,避免分布式环境里大量级联失败。

2.RPC调用能够相互隔离。

为每一个目标服务维护着一个线程池(或信号量),即使其中某个目标服务的调用资源被耗尽,也不会影响对其他服务的RPC调用。

当目标服务的线程池(或信号量)被耗尽时,拒绝RPC调用。

3.能够快速地降级和恢复。

当RPC目标服务故障时,能够快速和优雅地降级;当RPC目标服务失效后又恢复正常时,快速恢复。

4.能够对RPC调用提供接近实时的监控和警报。

监控信息包括请求成功、请求失败、请求超时和线程拒绝。如果对特定服务RPC调用的错误百分比超过阈值,后续的RPC调用就会自动失败,一段时间内停止对该服务的所有请求。

Spring Cloud在调用处理器中是使用HystrixCommand命令封装RPC调用,从而实现RPC保护。

HystrixCommand

Hystrix使用命令模式并结合RxJava的响应式编程和滑动窗口技术实现了对外部服务RPC调用的保护。

Hystrix实现了HystrixCommandHystrixObservableCommand两个命令类,用于封装需要保护的RPC调用。

HystrixObservableCommand命令不具备同步执行的能力,只具备异步执行能力,

HystrixCommand命令即具备同步执行的能力有具备异步执行的能力,并且Spring Cloud中重点使用HystrixCommand命令,因此本篇将以HystrixCommand命令为重点介绍Hystrix的原理和使用。

二、HystrixCommand的使用

依赖

如果不是在Spring Cloud的开发环境中使用HystrixCommand命令,就需要增加其Maven的依赖坐标

<dependency>
 <groupId>com.netflix.hystrix</groupId>
 <artifactId>hystrix-core</artifactId>
</dependency>

独立使用HystrixCommand命令主要有以下两个步骤

1.继承HystrixCommand类,

将正常的业务逻辑实现在继承的run方法中,

将回退的业务逻辑实现在继承的getFallback方法中。

2.使用HystrixCommand类提供的启动方法启动命令的执行。

使用HystrixCommand命令时,需要通过它的启动方法(如execute)来启动其执行(类似Thread通过start方法启动run方法的执行)

例子

下面通过继承HystrixCommand创建一个简单的HTTP请求命令,并且对HTTP请求过程中
执行的总次数、失败的总次数进行统计,具体的代码如下:

@Slf4j
public class HttpGetterCommand extends HystrixCommand<String>
{
     private String url;
     //run方法是否执行
     private boolean hasRun = false;
     //执行的次序
     private int index;
     //执行的总次数,线程安全
     private static AtomicInteger total = new AtomicInteger(0);
     //失败的总次数,线程安全
     private static AtomicInteger failed = new AtomicInteger(0);
     public HttpGetterCommand(String url, Setter setter){
        super(setter);
        this.url = url;
     }
     @Override
     protected String run() throws Exception{
        hasRun = true;
        index = total.incrementAndGet();
        log.info("req{} begin...", index);
        String responseData = HttpRequestUtil.simpleGet(url);
        log.info(" req{} end: {}", index, responseData);
        return "req" + index + ":" + responseData;
     }
     @Override
     protected String getFallback(){
         //是否直接失败
         boolean isFastFall = !hasRun;
         if (isFastFall){
            index = total.incrementAndGet();
         }
         if (super.isCircuitBreakerOpen()){
            HystrixCommandMetrics.HealthCounts hc = 
            super.getMetrics().getHealthCounts();
            log.info("window totalRequests:{},errorPercentage:{}",
            hc.getTotalRequests(), //滑动窗口总的请求数
            hc.getErrorPercentage()); //滑动窗口出错比例
         }
         //熔断器是否打开
         boolean isCircuitBreakerOpen = isCircuitBreakerOpen();
         log.info("req{} fallback: 熔断{},直接失败 {},失败次数{}",
         index,
         isCircuitBreakerOpen,
         isFastFall,
         failed.incrementAndGet());
         return "req" + index + ":调用失败";
     }
}

HttpGetterCommand的测试用例代码

@Slf4j
public class HystryxCommandExcecuteDemo
{
 
     /***测试HttpGetterCommand */
     @Test
     public void testHttpGetterCommand() throws Exception{

     /**
     *构造配置实例
     */
     HystrixCommand.Setter setter = HystrixCommand.Setter
     .withGroupKey(HystrixCommandGroupKey.Factory.asKey("group-1"))
     .andCommandKey(HystrixCommandKey.Factory.asKey("command-1"))
     .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("threadPool-1"));

     /**测试HttpGetterCommand */
     // 在构造函数中,使用HystrixCommand.Setter配置实例对该基类的实例进行了初始化。
     String result =new HttpGetterCommand(HELLO_TEST_URL, setter).execute();

     log.info("result={}", result);
 }
}

用例中首先构造了一个配置实例setter,配置了非常基础的命令组Key(GroupKey)、命令Key(CommandKey)、线程池Key(ThreadPoolKey)3个配置项,

然后创建了HttpGetterCommand实例并使用execute()执行该命令。

HystrixCommand的配置内容和方式 

使用HystrixCommand.Setter配置实例进行配置 

也就是上面代码中的:

HystrixCommand.Setter setter = HystrixCommand.Setter
     .withGroupKey(HystrixCommandGroupKey.Factory.asKey("group-1"))
     .andCommandKey(HystrixCommandKey.Factory.asKey("command-1"))
     .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("threadPool-1"));

其中涉及以下3个配置项:

1.CommandKey:该命令的名称。

2.GroupKey:该命令属于哪一个组,以帮助我们更好地组织命令。

3.ThreadPoolKey:该命令所属线程池的名称。

相同的线程池名称会共享同一线程池,若不进行配置,则默认使用GroupKey作为线程池名称。

除此之外,还可以通过HystrixCommand.Setter配置实例,整体设置一些其他的属性集合,如:

1.CommandProperties:与命令执行相关的一些属性集,包括降级设置、熔断器的配置、隔离策略以及一些监控指标配置项等。

2.ThreadPoolProperties:与线程池相关的一些属性集,包括线程池大小、排队队列的大小等。 

配置的例子

public class SetterDemo{
     public static HystrixCommand.Setter buildSetter(String groupKey,String commandKey,String threadPoolKey){
         /**
         *与命令执行相关的一些属性集
         */
         HystrixCommandProperties.Setter commandSetter =HystrixCommandProperties.Setter()
         //至少有3个请求,熔断器才达到熔断触发的次数阈值
         .withCircuitBreakerRequestVolumeThreshold(3)
         //熔断器中断请求5秒后会进入half-open状态,尝试放行
         .withCircuitBreakerSleepWindowInMilliseconds(5000)
         //错误率超过60%,快速失败
         .withCircuitBreakerErrorThresholdPercentage(60)
         //启用超时
         .withExecutionTimeoutEnabled(true)
         //执行的超时时间,默认为1000ms
         .withExecutionTimeoutInMilliseconds(5000)
         //可统计的滑动窗口内的buckets数量,用于熔断器和指标发布
         .withMetricsRollingStatisticalWindowBuckets(10)
         //可统计的滑动窗口的时间长度
         //这段时间内的执行数据用于熔断器和指标发布
         .withMetricsRollingStatisticalWindowInMilliseconds(10000);
 
         /**
         *线程池配置
         */
         HystrixThreadPoolProperties.Setter poolSetter =
         HystrixThreadPoolProperties.Setter()
         //这里我们设置了线程池大小为5
         .withCoreSize(5)
         .withMaximumSize(5);
         /**
         *与线程池相关的一些属性集
         */
         HystrixCommandGroupKey hGroupKey = HystrixCommandGroupKey.Factory.
        asKey(groupKey);
         HystrixCommandKey hCommondKey = HystrixCommandKey.Factory.
        asKey(commandKey);
         HystrixThreadPoolKey hThreadPoolKey = HystrixThreadPoolKey.Factory.
        asKey(threadPoolKey);
         HystrixCommand.Setter outerSetter = HystrixCommand.Setter
         .withGroupKey(hGroupKey)
         .andCommandKey(hCommondKey)
         .andThreadPoolKey(hThreadPoolKey)
         .andCommandPropertiesDefaults(commandSetter)
         .andThreadPoolPropertiesDefaults(poolSetter);
         return outerSetter;
     }
}

使用Hystrix提供的ConfigurationManager配置管理类的工厂实例对HystrixCommand命令的执行参数进行配置

//熔断器的请求次数阈值:大于3次请求
 ConfigurationManager.getConfigInstance().setProperty("hystrix.command.default.circuitBreaker.requestVolumeThreshold", 3);

Spring Cloud Hystrix所使用的正是这种配置方法

三、HystrixCommand命令的执行方法

HystrixCommand提供了4个执行启动的方法:

execute()

以同步堵塞方式执行run()。

一旦开始执行该命令,当前线程就会阻塞,直到该命令返回结果,然后才能继续执行下面的逻辑。

实际上在内部使用queue().get()的方式完成同步调用的

queue()

异步非阻塞方式执行run()方法,该方法直接返回一个Future对象。

可通过Future.get()拿到run()的返回结果,但Future.get()是阻塞执行的

observe()

返回一个响应式编程Observable主题(这是一个热主题(Hot Observable)),可以为该主题对象注册上Subscriber观察者回调实例,或者注册上Action1不完全回调实例来响应式处理命令的执行结果。

如果HystrixCommand的run()方法执行成功,就会触发订阅者的onNext()和onCompleted()回调方法,如果执行异常,就会触发订阅者的onError()回调方法。

什么是热主题呢?

就是无论主题是否存在观察者订阅,都会自动触发执行它的run()方法。

另外还有一点,observe()方法所返回的主题可以重复订阅

toObservable()

返回一个响应式编程Observable主题。

同样可以为该主题对象注册上Subscriber观察者回调实例,或者注册上Action1不完全回调实例,来响应式处理命令的执行结果。

不过,与observe()返回的主题不同,toObservable主题返回的是冷主题,并且只能被订阅一次

什么是冷主题(Cold Observable)?

就是在获取主题的时候不会立即触发执行,只有在观察者订阅时才会执行内部的HystrixCommand命令的run()方法。

 四、HystrixCommand命令的执行流程

spring cloud里的rpc框架_ide

简单的流程图

spring cloud里的rpc框架_RPC_02

在获取HystrixCommand命令的执行结果时,无论是使用execute()、toObservable()方法,还是使用observe()方法,最终都会通过执行HystrixCommand.toObservable()订阅执行结果和返回。

在Hystrix内部,调用toObservable()方法返回一个观察的主题,当Subscriber订阅者订阅主题后,HystrixCommand会弹射一个事件,然后通过一系列的判断,顺序依次是缓存是否命中、熔断器是否打开、线程池是否占满,开始执行实际的HystrixCommand.run()方法。

该方法的实现主要为异步处理的业务逻辑,如果在这其中任何一个环节出现错误或者抛出异常,就会回退到getFallback()方法进行服务降级处理,当降级处理完成之后,会将结果返回给实际的调用者。

HystrixCommand的工作流程总结

1.判断是否使用缓存响应请求,若启用了缓存,且缓存可用,则直接使用缓存响应请求。Hystrix支持请求缓存,但需要用户自定义启动。

2.判断熔断器是否开启,如果熔断器处于open状态,则跳到第5步。

3.若使用线程池进行请求隔离,则判断线程池是否已占满,若已满则跳到第5步;若使用信号量进行请求隔离,则判断信号量是否耗尽,若耗尽则跳到第5步。

4.使用HystrixCommand.run()方法执行具体业务逻辑,如果执行失败或者超时,就跳到第5步,否则跳到第6步。

5.执行HystrixCommand.getFallback()服务降级处理逻辑。

6.返回请求响应。 

什么场景下会触发fallback方法呢?

 

spring cloud里的rpc框架_ide_03

五、RPC保护之舱壁模式

1.什么是舱壁模式

船舶工业为了使船不容易沉没,使用舱壁将船舶划分为几个部分,以便在船体破坏的情况下可以将船舶各个部分密封起来。

在RPC调用过程中,使用舱壁模式可以保护有限的系统资源不被耗尽。

在一个基于微服务的应用程序中,通常需要调用多个服务提供者的接口才能完成一个特定任务。

不使用舱壁模式,所有的RPC调用都从同一个线程池中获取线程,一个具体的实例如图所示。

spring cloud里的rpc框架_线程池_04

在该实例中,服务提供者Provider A对依赖的Provider B、Provider C、Provider D的所有RPC调用都从公共的线程池获取线程。

在高服务器请求的情况下,对某个性能较低的服务提供者的RPC调用很容易“霸占”整个公共的RPC线程池,对其他性能正常的服务提供者的RPC调用往往需要等待线程资源的释放。最后,整个Web容器(Tomcat)会崩溃。

为了最大限度地减少Provider之间的相互影响,一个更好的做法是:

对于不同的服务提供者可以设置不同的RPC调用线程池,让不同RPC通过专门的线程池请求到各自的Provider服务提供者,像舱壁一样对Provider进行隔离。

对于不同的服务提供者设置不同的RPC调用线程池,这种模式被称为舱壁模式,如图所示

spring cloud里的rpc框架_Hystix原理_05

使用舱壁可以避免对单个Provider的RPC消耗掉所有资源,从而防止由于某一个服务性能低而引起的级联故障和雪崩效应。

一般来说,RPC线程与Web容器的IO线程也是需要隔离的。如下图所示

spring cloud里的rpc框架_线程池_06

当Provider A的用户请求涉及Provider B和Provider C的RPC时,Provider A的IO线程会将任务交给对应的RPC线程池里面的RPC线程来执行,Provider A的IO线程就可以去干别的事情去了,当RPC线程执行完远程调用的任务之后,就会将调用的结果返回给IO线程。

如果RPC线程池耗尽了,IO线程池也不会受到影响,从而实现RPC线程与Web容器的IO线程的相互隔离。

Hystrix提供了两种RPC隔离方式:线程池隔离和信号量隔离

2.Hystrix线程池隔离

Hystrix既可以为HystrixCommand命令默认创建一个线程池,又可以关联上一个指定的线程池。

每一个线程池都有一个Key,名为Thread Pool Key(线程池名)。

如果没有为HystrixCommand指定线程池,Hystrix就会为HystrixCommand创建一个与GroupKey(命令组Key)同名的线程池,当然,如果与Group Key同名的线程池已经存在,就直接进行关联。

也就是说,默认情况下,HystrixCommand命令的Thread Pool Key与GroupKey是相同的。

总体来说,线程池是Hystrix中RPC调用隔离的关键,所有的监控、调用、缓存等都围绕线程池展开。

如果要指定线程池,可以在Setter中定制线程池的Key和属性

默认情况下,在Spring Cloud中,Hystrix会为每一个Command Group Key自动创建一个同名的线程池。

而在Hystrix客户端,每一个RPC目标Provider的Command Group Key默认值为它的应用名称(Application Name)

如果某个应用(如uaa-provider)需要发起对demo-provider的远程调用,那么Hystrix为该Provider创建的RPC线程池的名称默认为demo-provider,专门用于对demo-provider的REST服务进行RPC调用和隔离

3.Hystrix线程池隔离配置

在Spring Cloud服务提供者中,如果需要使用Hystrix线程池进行RPC隔离,就可以在应用的配置文件中进行相应的配置。

下面是demo-provider的RPC线程池配置的实例

hystrix:
  threadpool:
    default:
      coreSize: 10 #线程池核心线程数
      maximumSize: 20 #线程池最大线程数
      allowMaximumSizeToDivergeFromCoreSize: true #线程池maximumSize最大线程数是否生效
      keepAliveTimeMinutes:10 #设置可空闲时间,单位为分钟
   command:
     default: #全局默认配置
       execution: #RPC隔离的相关配置
         isolation:
           strategy: THREAD #配置请求隔离的方式,这里为线程池方式
           thread:
           timeoutInMilliseconds: 100000 #RPC执行的超时时间,默认为1000毫秒
           interruptOnTimeout: true #发生超时后是否中断方法的执行,默认值为true

对上面的实例中用到的与Hystrix线程池有关的配置项介绍如下:

1.hystrix.threadpool.default.coreSize:设置线程池的核心线程数。

2.hystrix.threadpool.default.maximumSize:设置线程池的最大线程数

起作用的前提是allowMaximumSizeToDivergeFromCoreSize的属性值为true。
maximumSize属性值可以等于或者大于coreSize值,当线程池的线程不够用时,Hystrix会创建新的线程,直到线程数达到maximumSize的值,创建的线程为非核心线程。

3.hystrix.threadpool.default.allowMaximumSizeToDivergeFromCoreSize:该属性允许maximumSize起作用。

4.hystrix.threadpool.default.keepAliveTimeMinutes:该属性设置非核心线程的存活时间。

如果某个非核心线程的空闲超过keepAliveTimeMinutes设置的时间,非核心线程就会被释放。

其单位为分钟,默认值为1,默认情况下,表示非核心线程空闲1分钟后释放。

5.hystrix.command.default.execution.isolation.strategy:该属性设置RPC远程调用HystrixCommand命令的隔离策略。

它有两个可选值:THREAD和SEMAPHORE,默认值为THREAD。

THREAD表示使用线程池进行RPC隔离,SEMAPHORE表示通过信号量来进行RPC隔离和限制并发量。

6.hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds:设置调用者等待HystrixCommand命令执行的超时限制。

超过此时间,HystrixCommand被标记为TIMEOUT,并执行回退逻辑。

超时会作用在HystrixCommand.queue(),即使调用者没有调用get()去获得Future对象。

 

以上配置是application应用级别的默认线程池配置,覆盖的范围为系统中的所有RPC线程池。

另一种配置方式

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
 
import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.*;
 
/*
 * 注意: @HystrixCommand 注解方式依赖 AOP, 不支持在同一个类的内部方法之间直接调用, 必须将被调用类作为 bean 注入并调用
 */
public class DemoCircuitBreakerAnnotation {
 
    /**
     * 使用 THREAD 模式及线程池参数、通用参数说明
     */
    @HystrixCommand(
            groupKey = "GroupAnnotation",
            commandKey = "HystrixAnnotationThread",
            fallbackMethod = "HystrixAnnotationThreadFallback",
            /*
             * 线程池名, 具有同一线程池名的方法将在同一个线程池中执行
             *
             * 默认值: 方法的groupKey
             */
            threadPoolKey = "GroupAnnotationxThreadPool",
            threadPoolProperties = {
                /*
                 * 线程池Core线程数及最大线程数
                 *
                 * 默认值: 10
                 */
                @HystrixProperty(name = CORE_SIZE, value = "10"),
                /*
                 * 线程池线程 KeepAliveTime 单位: 分钟
                 *
                 * 默认值: 1
                 */
                @HystrixProperty(name = KEEP_ALIVE_TIME_MINUTES, value = "1"),
                /*
                 * 线程池最大队列长度
                 *
                 * 默认值: -1, 此时使用 SynchronousQueue
                 */
                @HystrixProperty(name = MAX_QUEUE_SIZE, value = "100"),
                /*
                 * 达到这个队列长度后, 线程池开始拒绝后续任务
                 *
                 * 默认值: 5, MaxQueueSize > 0 时有效
                 */
                @HystrixProperty(name = QUEUE_SIZE_REJECTION_THRESHOLD, value = "90"),
            },
            commandProperties = {
                /*
                 * 以 THREAD (线程池)模式执行, run 方法将被一个线程池中的线程执行
                 *
                 * 注意: 由于有额外的线程调度开销, THREAD 模式的性能不如 NONE 和 SEMAPHORE 模式, 但隔离性比较好
                 *
                 * 默认值: THREAD
                 */
                @HystrixProperty(name = EXECUTION_ISOLATION_STRATEGY, value = "THREAD"),
                /*
                 * 方法执行超时后是否中断执行线程
                 *
                 * 默认值: true, THREAD 模式下有效
                 */
                @HystrixProperty(name = EXECUTION_ISOLATION_THREAD_INTERRUPT_ON_TIMEOUT, value = "true"),
                /*
                 * 超时时间参数
                 * 在 THREAD 模式下, 方法超时后 Hystrix 默认会中断原方法的执行线程, 并标记这次方法的执行结果为失败(影响方法的健康值)
                 * 同时另开一个线程执行 fallback, 最终返回 fallback 的结果
                 *
                 * 默认值: 1000
                 */
                @HystrixProperty(name = EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "500")
                /*
                 * 其余参数参考上面的例子, 或者使用默认值
                 */
            })
    public String HystrixAnnotationThread(String param) {
        return "Run with " + param;
    }
    public String HystrixAnnotationThreadFallback(String param, Throwable ex) {
        return String.format("Fallback with param: %s, exception: %s", param, ex);
    }
}

4.Hystrix信号量隔离

除了使用线程池进行资源隔离之外,Hystrix还可以使用信号量机制完成资源隔离。

信号量所起到的作用就像一个开关,而信号量的值就是每个命令的并发执行数量,当并发数高于信号量的值时就不再执行命令。

比如,如果Provider A的RPC信号量大小为10,那么它同时只允许有10个RPC线程来访问服务Provider A,其他的请求都会被拒绝,从而达到资源隔离和限流保护的作用。

Hystrix信号量机制不提供专用的线程池,也不提供额外的线程,在获取到信号量之后,执行HystrixCommand命令逻辑的线程还是之前Web容器的IO线程。

实际RPC远程调用最终是由Web容器的IO线程来完成,这样就带来了一个问题

由于RPC远程调用是一种耗时的操作,如果IO线程被长时间占用,就会导致Web容器请求处理能力下降,

甚至会在一段时间内因为IO线程被占满而造成Web容器无法对新的用户请求及时响应,最终导致Web容器崩溃。

所以,信号量隔离机制不适用于RPC隔离。 

执行过程

spring cloud里的rpc框架_spring cloud里的rpc框架_07

信号量可以细分为run执行信号量和fallback回退信号量

IO线程在执行HystrixCommand命令之前需要抢到run执行信号量,成功之后才允许执行HystrixCommand.run()方法。

如果争抢失败,就准备回退,

但是在执行HystrixCommand.getFallback()回退方法之前,还需要争抢fallback回退信号量,成功之后才允许执行HystrixCommand.getFallback()回退方法。

如果都获取失败,操作就会直接终止。

5.Hystrix信号量隔离的配置

涉及Hystrix命令属性配置器HystrixCommandProperties.Setter()的实例方法如下:

1.withExecutionIsolationSemaphoreMaxConcurrentRequests(int)

此方法设置执行信号量的大小,也就是HystrixCommand.run()方法允许的最大请求数。

如果达到最大请求数,后续的请求就会被拒绝。

在Web容器中,抢占信号量的线程应该是容器(比如Tomcat)IO线程池中的一小部分,所以信号量的数量不能大于容器线程池的大小,否则就起不到保护作用。

执行信号量的大小默认值为10

2.withFallbackIsolationSemaphoreMaxConcurrentRequests(int)

此方法设置回退信号量的大小,也就是HystrixCommand.getFallback()方法允许的最大请求数。

如果达到最大请求数,后续的回退请求就会被拒绝。

另一种配置方式

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
 
import static com.netflix.hystrix.contrib.javanica.conf.HystrixPropertiesManager.*;
 
/*
 * 注意: @HystrixCommand 注解方式依赖 AOP, 不支持在同一个类的内部方法之间直接调用, 必须将被调用类作为 bean 注入并调用
 */
public class DemoCircuitBreakerAnnotation {
 
    /**
     * 使用 SEMAPHORE 模式及通用参数说明
     */
    @HystrixCommand(
            groupKey = "GroupAnnotation",
            commandKey = "HystrixAnnotationSemaphore",
            fallbackMethod = "HystrixAnnotationSemaphoreFallback",
            commandProperties = {
                /*
                 * 以 SEMAPHORE (信号量)模式执行, 原方法将在调用此方法的线程中执行
                 *
                 * 如果原方法无需信号量限制, 可以选择使用 NONE 模式
                 * NONE 模式相比 SEMAPHORE 模式少了信号量获取和判断的步骤, 效率相对较高, 其余执行流程与 SEMAPHORE 模式相同
                 *
                 * 默认值: THREAD
                 */
                @HystrixProperty(name = EXECUTION_ISOLATION_STRATEGY, value = "SEMAPHORE"),
                /*
                 * 执行 run 方法的信号量上限, 即由于方法执行未完成停留在 run 方法内的线程最大个数
                 * 执行线程退出 run 方法后释放信号量, 其他线程获取不到信号量无法执行 run 方法
                 *
                 * 默认值: 1000, SEMAPHORE 模式下有效
                 */
                @HystrixProperty(name = EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, value = "100"),
                /*
                 * 执行 fallback 方法的信号量上限
                 *
                 * 注意: 所有模式(NONE|SEMAPHORE|THREAD) fallback 的执行都受这个参数影响
                 *
                 * 默认值: Integer.MAX_VALUE
                 */
                @HystrixProperty(name = FALLBACK_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, value = "1000"),
                /*
                 * 超时时间参数
                 * 在 SEMAPHORE 模式下, 方法超时后 Hystrix 不会中断原方法的执行线程, 只标记这次方法的执行结果为失败(影响方法的健康值)
                 * 同时另开一个线程执行 fallback, 最终返回 fallback 的结果
                 *
                 * 默认值: 1000
                 */
                @HystrixProperty(name = EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS, value = "500"),
                /*
                 * 方法各项指标值存活的滑动时间窗口长度, 每经过一个时间窗口长度重置各项指标值, 比如: 方法的健康值
                 *
                 * 默认值: 10000
                 */
                @HystrixProperty(name = METRICS_ROLLING_STATS_TIME_IN_MILLISECONDS, value = "10000"),
                /*
                 * 滑动时间窗口指标采样的时间分片数, 分片数越高时, 指标汇总更新的频率越高, 指标值的实时度越好, 但同时也占用较多 CPU
                 * 采样过程: 将一个滑动时间窗口时长根据分片数等分成多个时间分片, 每经过一个时间分片将最新一个时间分片的内积累的统计数据汇总更新到时间窗口内存活的已有指标值中
                 *
                 * 注意: 这个值只影响 Hystrix Monitor 上方法指标值的展示刷新频率,不影响熔断状态的判断
                 *
                 * 默认值: 10
                 */
                @HystrixProperty(name = METRICS_ROLLING_STATS_NUM_BUCKETS, value = "10"),
                /*
                 * 健康值采样的间隔, 相当于时间片长度, 每经过一个间隔将这个时间片内积累的统计数据汇总更新到时间窗口内存活的已有健康值中
                 *
                 * 健康值主要包括: 方法在滑动时间窗口内的总执行次数、成功执行次数、失败执行次数
                 *
                 * 默认值: 500
                 */
                @HystrixProperty(name = METRICS_HEALTH_SNAPSHOT_INTERVAL_IN_MILLISECONDS, value = "500"),
                /*
                 * 一个滑动时间窗口内, 方法的执行次数达到这个数量后方法的健康值才会影响方法的熔断状态
                 *
                 * 默认值: 20
                 */
                @HystrixProperty(name = CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD, value = "10"),
                /*
                 * 一个采样滑动时间窗口内, 方法的执行失败次数达到这个百分比且达到上面的执行次数要求后, 方法进入熔断状态, 后续请求将执行 fallback 流程
                 *
                 * 默认值: 50
                 */
                @HystrixProperty(name = CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE, value = "50"),
                /*
                 * 熔断状态停留时间, 方法进入熔断状态后需要等待这个时间后才会再次尝试执行原方法重新评估健康值. 再次尝试执行原方法时若请求成功则重置健康值
                 *
                 * 默认值: 5000
                 */
                @HystrixProperty(name = CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS, value = "5000")
            })
    public String HystrixAnnotationSemaphore(String param) {
        return "Run with " + param;
    }
    public String HystrixAnnotationSemaphoreFallback(String param, Throwable ex) {
        return String.format("Fallback with param: %s, exception: %s", param, ex);
    }
}

6.信号量隔离与线程池隔离的区别

spring cloud里的rpc框架_RPC_08

六、RPC保护之熔断器模式

熔断器的工作机制

统计最近RPC调用发生错误的次数,然后根据统计值中的失败比例等信息决定是否允许后面的RPC调用继续,或者快速地失败回退。

熔断器的3种状态

1.closed:熔断器关闭状态

这也是熔断器的初始状态,此状态下RPC调用正常放行。

2.open:失败比例到一定的阈值之后,熔断器进入开启状态

此状态下RPC将会快速失败,执行失败回退逻辑。

3.half-open:在打开open状态一定时间之后(睡眠窗口结束),熔断器进入半开启状态

小流量尝试进行RPC调用放行。

如果尝试成功,熔断器就变为closed状态,RPC调用正常;如果尝试失败,熔断器就变为open状态,RPC调用快速失败。

熔断器状态之间相互转换的逻辑关系图

spring cloud里的rpc框架_ide_09

断路器重要配置说明

HystrixCommandProperties.Setter propertiesSetter =HystrixCommandProperties.Setter()
 //至少有3个请求,熔断器才达到熔断触发的次数阈值
 .withCircuitBreakerRequestVolumeThreshold(3)
 //熔断器中断请求5秒后会进入half-open状态,尝试放行
 .withCircuitBreakerSleepWindowInMilliseconds(5000)
 //错误率超过60%,快速失败
 .withCircuitBreakerErrorThresholdPercentage(60)
 //启用超时
 .withExecutionTimeoutEnabled(true)
 //执行的超时时间,默认为1000毫秒(ms),这里设置为500毫秒
 .withExecutionTimeoutInMilliseconds(500)
 //可统计的滑动窗口内的buckets数量,用于熔断器和指标发布
 .withMetricsRollingStatisticalWindowBuckets(10)
 //可统计的滑动窗口的时间长度
 //这段时间内的执行数据用于熔断器和指标发布
 .withMetricsRollingStatisticalWindowInMilliseconds(10000);

 有以下配置器的命令配置需要重点说明一下:

1.通过withExecutionTimeoutInMilliseconds(int)方法将默认为1000毫秒的执行超时上限设置为500毫秒

也就是说,只要HystrixCommand.run()的执行时间超过500毫秒,就会触发Hystrix超时回退

2.通过withCircuitBreakerRequestVolumeThreshold(int)方法将熔断器触发熔断的最少请求次数的默认值20次改为了3次

3.通过withCircuitBreakerErrorThresholdPercentage(int)方法设置错误率阈值百分比的值为60

在滑动窗口时间内,当错误率超过此值时,熔断器进入open状态,所有请求都会触发失败回退(fallback),错误率阈值百分比的默认值为50。

4. 通过withCircuitBreakerSleepWindowInMilliseconds(5000)

在命令的熔断器打开后,熔断器默认会有5秒的睡眠等待时间,在这段时间内的所有请求直接执行回退方法;

5秒之后,熔断器会进入half-open状态,尝试放行一次命令执行,如果成功就关闭熔断器,状态转成closed,否则熔断器回到open状态。