RPC保护之熔断器模式
===========
熔断器的工作机制为:统计最近RPC调用发生错误的次数,然后根据统计值中的失败比例等信息决定是否允许后面的RPC调用继续,或者快速地失败回退。熔断器的3种状态如下:
(1)closed:熔断器关闭状态,这也是熔断器的初始状态,此状态下RPC调用正常放行。
(2)open:失败比例到一定的阈值之后,熔断器进入开启状态,此状态下RPC将会快速失败,执行失败回退逻辑。
(3)half-open:在打开一定时间之后(睡眠窗口结束),熔断器进入半开启状态,小流量尝试进行RPC调用放行。如果尝试成功,熔断器就变为closed状态,RPC调用正常;如果尝试失败,熔断器就变为open状态,RPC调用快速失败。
熔断器状态之间相互转换的逻辑关系如图5-10所示。
图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
{
//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 isF
【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
astFall = !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所示。
图5-11 TakeTimeDemoCommand的熔断器健康统计滑动窗口的状态转换关系 图
大家已经知道,Hystrix熔断器的配置除了代码方式外,还有properties文本属性配置的方式;另外,Hystrix熔断器相关的滑动窗口不止一个基础的健康统计滑动窗口,还包含一个百分比命令执行时间统计滑动窗口,两个窗口都可以进行配置。
下面以文本属性配置方式为主,对Hystrix基础的健康统计滑动窗口的配置进行详细介绍。
(1)hystrix.command.default.metrics.rollingStats.timeInMilliseconds:
设置健康统计滑动窗口的持续时间(以毫秒为单位),默认值为10 000毫秒。熔断器的打开会根据一个滑动窗口的统计值来计算,若滑动窗口时间内的错误率超过阈值,则熔断器将进入open状态。滑动窗口将被进一步细分为时间桶,滑动窗口的统计值等于窗口内所有时间桶的统计信息的累加,每个时间桶的统计信息包含请求成功(success)、失败(failure)、超时(timeout)、被拒(rejection)的次数。
此选项通过代码方式配置时所对应的函数如下: