你可能该来学习Hystrix RPC保护的原理,RPC保护之熔断器模式了

RPC保护之熔断器模式

熔断器的工作机制为:统计最近RPC调用发生错误的次数,然后根据统计值中的失败比例等信息决定是否允许后面的RPC调用继续,或者快速地失败回退。熔断器的3种状态如下:

(1)closed:熔断器关闭状态,这也是熔断器的初始状态,此状态下RPC调用正常放行。

(2)open:失败比例到一定的阈值之后,熔断器进入开启状态,此状态下RPC将会快速失败,执行失败回退逻辑。

(3)half-open:在打开一定时间之后(睡眠窗口结束),熔断器进入半开启状态,小流量尝试进行RPC调用放行。如果尝试成功,熔断器就变为closed状态,RPC调用正常;如果尝试失败,熔断器就变为open状态,RPC调用快速失败。

熔断器状态之间相互转换的逻辑关系如图5-10所示。

你可能该来学习Hystrix RPC保护的原理,RPC保护之熔断器模式了

图5-10 熔断器状态之间的转换关系详细图

熔断器状态变化的演示实例

为了观察熔断器的状态变化,这里通过继承HystrixCommand类特别设计了一个能够设置运行时长的自定义命令类TakeTimeDemoCommand,通过设置其运行占用时间takeTime成员的值可以控制其运行过程中是否超时。演示实例的代码如下:

package com.crazymaker.demo.hystrix;
//省略import
@Slf4j
public class CircuitBreakerDemo
{
 //执行的总次数,线程安全
 private static AtomicInteger total = new AtomicInteger(0);
 /**
 *内部类:一个能够设置运行时长的自定义命令类
 */
 static class TakeTimeDemoCommand extends HystrixCommand<String>
 {
 //run方法是否执行
 private boolean hasRun = false;
 //执行的次序
 private int index;
 //运行的占用时间
 long takeTime;
 public TakeTimeDemoCommand(long takeTime, Setter setter)
 {
 super(setter);
 this.takeTime = takeTime;
 }
 @Override
 protected String run() throws Exception
 {
 hasRun = true;
 index = total.incrementAndGet();
 Thread.sleep(takeTime);
 HystrixCommandMetrics.HealthCounts hc =
super.getMetrics().getHealthCounts();
 log.info("succeed- req{}:熔断器状态:{}, 失败率:{}%",
 index, super.isCircuitBreakerOpen(),
hc.getErrorPercentage());
 return "req" + index + ":succeed";
 }
 @Override
 protected String getFallback()
 {
 //是否直接失败
 boolean isFastFall = !hasRun;
 if (isFastFall)
 {
 index = total.incrementAndGet();
 }
 HystrixCommandMetrics.HealthCounts hc =
super.getMetrics().getHealthCounts();
 log.info("fallback- req{}:熔断器状态:{}, 失败率:{}%",
 index, super.isCircuitBreakerOpen(),
hc.getErrorPercentage());
 return "req" + index + ":failed";
 }
 }
 /**
 *测试用例:熔断器熔断
 */
 @Test
 public void testCircuitBreaker() throws Exception
 {
 /**
命令参数配置 *命令参数配置
 */
 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);
 HystrixCommand.Setter rpcPool = HystrixCommand.Setter
 .withGroupKey(HystrixCommandGroupKey.Factory.asKey("group-1"))
 .andCommandKey(HystrixCommandKey.Factory.asKey("command-1"))
 .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("threadPool-1"))
 .andCommandPropertiesDefaults(propertiesSetter);
 /**
 *首先设置运行时间为800毫秒,大于命令的超时限制500毫秒
 */
 long takeTime = 800;
 for (int i = 1; i <= 10; i++)
 {
 TakeTimeDemoCommand command =
new TakeTimeDemoCommand(takeTime, rpcPool);
 command.execute();
 //健康信息
 HystrixCommandMetrics.HealthCounts hc =
command.getMetrics().getHealthCounts();
 if (command.isCircuitBreakerOpen())
 {
 /**
 *熔断之后,设置运行时间为300毫秒,小于命令的超时限制500毫秒
 */
 takeTime = 300;
 log.info("============ 熔断器打开了,等待休眠期(默认5秒)结束");
 /**
 *等待7秒之后,再一次发起请求
 */
 Thread.sleep(7000);
 }
 }
 Thread.sleep(Integer.MAX_VALUE);
 }
}

上面的演示程序中,有以下配置器的命令配置需要重点说明一下:

(1)通过
withExecutionTimeoutInMilliseconds(int)方法将默认为1000毫秒的执行超时上限设置为500毫秒,也就是说,只要TakeTimeDemoCommand.run()的执行时间超过500毫秒,就会触发Hystrix超时回退。

(2)通过
withCircuitBreakerRequestVolumeThreshold(int)方法将熔断器触发熔断的最少请求次数的默认值20次改为了3次,这样更容易测试。

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

执行上面的演示实例,运行的结果节选如下:

[HystrixTimer-1] INFO c.c.d.h.CircuitBreakerDemo - fallback- req1:熔断器状态:false, 失败率:0%
[HystrixTimer-1] INFO c.c.d.h.CircuitBreakerDemo - fallback- req2:熔断器状态:false, 失败率:100%
[HystrixTimer-2] INFO c.c.d.h.CircuitBreakerDemo - fallback- req3:熔断器状态:false, 失败率:100%
[HystrixTimer-1] INFO c.c.d.h.CircuitBreakerDemo - fallback- req4:熔断器状态:true, 失败率:100%
[main] INFO c.c.d.h.CircuitBreakerDemo - ============ 熔断器打开了,等待休眠期(默认5秒)结束
[hystrix-threadPool-1-5] INFO c.c.d.h.CircuitBreakerDemo - succeed- req5:熔断器状态:true, 失败率:100%
[hystrix-threadPool-1-6] INFO c.c.d.h.CircuitBreakerDemo - succeed- req6:熔断器状态:false, 失败率:0%
[hystrix-threadPool-1-7] INFO c.c.d.h.CircuitBreakerDemo - succeed- req7:熔断器状态:false, 失败率:0%
[hystrix-threadPool-1-8] INFO c.c.d.h.CircuitBreakerDemo - succeed- req8:熔断器状态:false, 失败率:0%
[hystrix-threadPool-1-9] INFO c.c.d.h.CircuitBreakerDemo - succeed- req9:熔断器状态:false, 失败率:0%
[hystrix-threadPool-1-10] INFO c.c.d.h.CircuitBreakerDemo - succeed- req10:熔断器状态:false, 失败率:0%

从上面的执行结果可以看出,在第4次请求req4时,熔断器才达到熔断触发的次数阈值3,由于前3次皆为超时失败,失败率同时也大于阈值60%,因此第4次请求执行之后,熔断器状态为open。

在命令的熔断器打开后,熔断器默认会有5秒的睡眠等待时间,在这段时间内的所有请求直接执行回退方法;5秒之后,熔断器会进入half-open状态,尝试放行一次命令执行,如果成功就关闭熔断器,状态转成closed,否则熔断器回到open状态。

在上面的程序中,在熔断器熔断之后,演示程序将命令的运行时间takeTime改成了300毫秒,小于命令的超时限制500毫秒。在等待7秒(相当于7000毫秒)之后,演示程序再一次发起请求,从运行结果可以看到,第5次请求req5执行成功了,这是一次half-open状态的尝试放行,请求成功之后,熔断器的状态转成了open,后续请求将继续放行。注意,演示程序的第5次请求req5后的熔断器状态值反应在第6次请求req6的执行输出中。

 熔断器和滑动窗口的配置属性

熔断器的配置包含滑动窗口的配置和熔断器自身的配置。Hystrix的健康统计是通过滑动窗口来完成的,其熔断器的状态变化也依据滑动窗口的统计数据,所以这里先介绍滑动窗口的配置。先来看两个概念:滑动窗口和时间桶(Bucket)。

1.滑动窗口

可以这么来理解滑动窗口:一位乘客坐在正在行驶的列车的靠窗座位上,列车行驶的公路两侧种着一排挺拔的白杨树,随着列车的前进,路边的白杨树迅速从窗口滑过,我们用每棵树来代表一个请求,用列车的行驶代表时间的流逝,列车上的这个窗口就是一个典型的滑动窗口,这个乘客能通过窗口看到的白杨树的数量就是滑动窗口要统计的数据。

2.时间桶

时间桶是统计滑动窗口数据时的最小单位。同样类比列车窗口,在列车速度非常快时,如果每掠过一棵树就统计一次窗口内树的数据,显然开销非常大,如果乘客将窗口分成N份,前进时列车每掠过窗口的N分之一就统计一次数据,开销就大大地减小了。简单来说,时间桶就是滑动窗口的N分之一。

熔断器的设置,代码方式可以使用
HystrixCommandProperties.Setter()配置器来完成,参考5.5.1节的实例,把自定义的TakeTimeDemoCommand中的Setter()配置器的相关参数配置如下:

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

在以上配置中,与熔断器的滑动窗口相关的配置具体含义如下:

(1)在滑动窗口中,最少有3个请求才会触发断路,默认值为20个。

(2)错误率达到60%时才可能触发断路,默认值为50%。

(3)断路之后的5000毫秒内,所有请求都直接调用getFallback()进行回退降级,不会调用run()方法;5000毫秒过后,熔断器变为half-open状态。

以上TakeTimeDemoCommand的熔断器滑动窗口的状态转换关系如图5-11所示。

你可能该来学习Hystrix RPC保护的原理,RPC保护之熔断器模式了

图5-11 TakeTimeDemoCommand的熔断器健康统计滑动窗口的状态转换关系 图

大家已经知道,Hystrix熔断器的配置除了代码方式外,还有properties文本属性配置的方式;另外,Hystrix熔断器相关的滑动窗口不止一个基础的健康统计滑动窗口,还包含一个百分比命令执行时间统计滑动窗口,两个窗口都可以进行配置。

下面以文本属性配置方式为主,对Hystrix基础的健康统计滑动窗口的配置进行详细介绍。

(1)hystrix.command.default.metrics.rollingStats.timeInMilliseconds:

设置健康统计滑动窗口的持续时间(以毫秒为单位),默认值为10 000毫秒。熔断器的打开会根据一个滑动窗口的统计值来计算,若滑动窗口时间内的错误率超过阈值,则熔断器将进入open状态。滑动窗口将被进一步细分为时间桶,滑动窗口的统计值等于窗口内所有时间桶的统计信息的累加,每个时间桶的统计信息包含请求成功(success)、失败(failure)、超时(timeout)、被拒(rejection)的次数。

此选项通过代码方式配置时所对应的函数如下:

HystrixCommandProperties.Setter().withMetricsRollingStatisticalWindowInMilliseconds(int)

(2)
hystrix.command.default.metrics.rollingStats.numBuckets:设置健康统计滑动窗口被划分的时间桶的数量,默认值为10。若滑动窗口的持续时间为默认的10 000毫秒,在默认情况下,一个时间桶的时间即1秒。若要做定制化的配置,则所设置的numBuckets(时间桶数量)的值和timeInMilliseconds(滑动窗口时长)的值有关联关系,必须符合timeInMilliseconds%numberBuckets==0的规则,否则会抛出异常。例如,二者的关联关系为70 000(滑动窗口70秒)%700(桶数)==0是可以的,但是70 000(滑动窗口70秒)%600(桶数)==400将抛出异常。

此选项通过代码方式配置时所对应的函数如下:
HystrixCommandProperties.Setter().withMetricsRollingStatisticalWindowBuckets(int)

(3)
hystrix.command.default.metrics.healthSnapshot.intervalInMilliseconds:设置健康统计滑动窗口拍摄运行状况统计指标的快照的时间间隔。

什么是拍摄运行状况统计指标的快照呢?就是计算成功和错误百分比这些影响熔断器状态的统计数据。

拍摄快照的时间间隔的单位为毫秒,默认值为500毫秒。由于统计指标的计算是一个消耗CPU的操作(即CPU密集型操作),也就是说,高频率地计算错误百分比等健康统计数据会占用很多CPU资源,因此在高并发RPC流量大的应用场景下可以适当调大拍摄快照的时间间隔。

此选项通过代码方式配置时所对应的函数如下:


HystrixCommandProperties.Setter().withMetricsHealthSnapshotIntervalInMilliseconds(int)Hystrix熔断器相关的滑动窗口不止一个基础的健康统计滑动窗口,还包含一个百分比命令执行时间统计滑动窗口。什么是“百分比命令执行时间”统计滑动窗口呢?该滑动窗口主要用于统计1%、10%、50%、90%、99%等一系列比例的命令执行平均耗时,主要用于生成统计图表。


hystrix.command.default.metrics.rollingPercentile前缀的配置项专门用于配置百分比命令执行时间统计滑动窗口。下面以文本属性配置方式为主对Hystrix“百分比命令执行时间”统计滑动窗口的配置进行详细介绍。

(1)
hystrix.command.default.metrics.rollingPercentile.enabled:该配置项用于设置百分比命令执行时间统计滑动窗口是否生效,命令的执行时间是否被跟踪,并且计算各个百分比(如1%、10%、50%、90%、99.5%等)的平均时间。该配置项默认为true。

(2)
hystrix.command.default.metrics.rollingPercentile.timeInMilliseconds:设置百分比命令执行时间统计滑动窗口的持续时间(以毫秒为单位),默认值为60 000毫秒。当然,此滑动窗口进一步被细分为时间桶,以便提高统计的效率。

本选项通过代码方式配置时所对应的函数如下:

HystrixCommandProperties.Setter().withMetricsRollingPercentileWindowInMilliseconds(int)

(3)
hystrix.command.default.metrics.rollingPercentile.numBuckets:设置百分比命令执行时间统计滑动窗口被划分的时间桶的数量,默认值为6。

此滑动窗口的默认持续时间为60 000毫秒,在默认情况下,一个时间桶的时间即10秒。若要做定制化的配置,则此窗口所设置的numBuckets(时间桶数量)的值和timeInMilliseconds(滑动窗口时长)的值有关联关系,必须符合timeInMilliseconds(滑动窗口时长)%numberBuckets==0的规则,否则将抛出异常。

本选项通过代码方式配置时所对应的函数如下:

HystrixCommandProperties.Setter().withMetricsRollingPercentileWindowBuckets(int)

(4)
hystrix.command.default.metrics.rollingPercentile.bucketSize:设置百分比命令执行时间统计滑动窗口的时间桶内最大的统计次数,若bucketSize为100,而桶的时长为1秒,这1秒里有500次执行,则只有最后100次执行的信息会被统计到桶里。增加此配置项的值会导致内存开销及其他计算开销上升,该配置项的默认值为100。

本选项通过代码方式配置时所对应的函数如下:

HystrixCommandProperties.Setter().withMetricsRollingPercentileBucketSize (int)

以上是Hystrix熔断器相关的滑动窗口的配置,接下来介绍熔断器本身的配置。


hystrix.command.default.circuitBreaker前缀的配置项专门用于对熔断器本身进行配置。下面以文本属性配置方式为主,对Hystrix熔断器的配置进行详细介绍。

(1)
hystrix.command.default.circuitBreaker.enabled:该配置用来确定是否启用熔断器,默认值为true。

本选项通过代码方式配置时所对应的函数如下:

HystrixCommandProperties.Setter().withCircuitBreakerEnabled (boolean)

(2)
hystrix.command.default.circuitBreaker.requestVolumeThreshold:该配置用于设置熔断器触发熔断的最少请求次数。如果设置为20,那么当一个滑动窗口时间内(比如10秒)收到19个请求时,即使19个请求都失败,熔断器也不会打开变成open状态,默认值为20。

此选项通过代码方式配置时所对应的函数如下:
HystrixCommandProperties.Setter().withCircuitBreakerRequestVolumeThreshold(int)

(3)
hystrix.command.default.circuitBreaker.errorThresholdPercentage:该配置用于设置错误率阈值,当健康统计滑动窗口的错误率超过此值时,熔断器进入open状态,所有请求都会触发失败回退(fallback),错误率阈值百分比的默认值为50。

本选项通过代码方式配置时所对应的函数如下:

HystrixCommandProperties.Setter().withCircuitBreakerErrorThresholdPercentage(int)

(4)
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds:此配置项指定熔断器打开后经过多长时间允许一次请求尝试执行。熔断器打开时,Hystrix会在经过一段时间后就放行一条请求,如果这条请求执行成功,就说明此时服务很可能已经恢复正常,会将熔断器关闭,如果这条请求执行失败,就认为目标服务依然不可用,熔断器继续保持打开状态。

该配置用于设置熔断器的睡眠窗口,具体指定熔断器打开之后过多长时间才允许一次请求尝试执行,默认值为5000毫秒,表示当熔断器开启后,5000毫秒内会拒绝所有的请求,5000毫秒之后,熔断器才会进入halfopen状态。

此选项通过代码方式配置时所对应的函数如下:


HystrixCommandProperties.Setter().withCircuitBreakerSleepWindowInMilliseconds (int)(5)hystrix.command.default.circuitBreaker.forceOpen:如果配置为true,熔断器就会被强制打开,所有请求将被触发失败回退(fallback)。此配置的默认值为false。

此选项通过代码方式配置时所对应的函数如下:

HystrixCommandProperties.Setter().withCircuitBreakerForceOpen (boolean)

下面是本书随书实例demo-provider中有关熔断器的配置,节选如下:

hystrix:
 ...
 command:
 ...
 default: #全局默认配置
 circuitBreaker: #熔断器相关配置
 enabled: true #是否启动熔断器,默认为true
 requestVolumeThreshold: 20 #启用熔断器功能窗口时间内的最小请求数
 sleepWindowInMilliseconds: 5000 #指定熔断器打开后多长时间内允许一次请求尝试执行
 errorThresholdPercentage:50 #窗口时间内超过50%的请求失败后就会打开熔断器
 metrics:
 rollingStats:
 timeInMilliseconds: 6000
 numBuckets: 10
 UserClient#detail(Long): #独立接口配置,格式为: 类名#方法名(参数类型列表)
 circuitBreaker: #熔断器相关配置
 enabled: true #是否使用熔断器,默认为true
 requestVolumeThreshold: 20 #窗口时间内的最小请求数
 sleepWindowInMilliseconds:5000 #打开后允许一次尝试的睡眠时间,默认配置为5秒
 errorThresholdPercentage: 50 #窗口时间内熔断器开启的错误比例,默认配置为50
 metrics:
 rollingStats:
 timeInMilliseconds: 10000 #滑动窗口时间
 numBuckets: 10 #滑动窗口的时间桶数

使用文本格式配置时,可以对熔断器的参数值进行默认配置,也可以对特定的RPC接口进行个性化配置。对熔断器的参数值进行配置时使用hystrix.command.default默认前缀,对特定的RPC接口进行个性化配置时使用
hystrix.command.FeignClient#Method格式的前缀。在上面的演示例子中,对远程客户端Feign接口UserClient中的detail(Long)方法做了个性化的熔断器配置,其配置项的前缀如下:hystrix.command.UserClient#detail(Long)

 Hystrix命令的执行流程

在获取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)返回请求响应。

以上流程如图5-12所示。

你可能该来学习Hystrix RPC保护的原理,RPC保护之熔断器模式了

图5-12 HystrixCommand的执行流程示意图

什么场景下会触发fallback方法呢?见表5-2。

你可能该来学习Hystrix RPC保护的原理,RPC保护之熔断器模式了

表5-2 触发fallback方法的场景

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值