一. 为什么要有断路器
当A服务调用B服务,由于网络原因或自身原因出现问题时,A就会等待B的相应,当有更多的服务器请求资源时,就会有更多的请求A等待B的响应,这样就会发生连锁效应(雪崩效应),因为服务A有太多的线程导致服务A线程耗尽从而导致A也无法接受请求出现问题,断路器就是解决这一问题的
二. 什么是断路器
断路器有三种正常状态:完全打开OPEN,半开HALF_OPEN,关闭CLOSED,还有两种通过设置的强制状态:强制不可用DISABLED,强制打开FORCED_OPEN。
三:滑动窗口的类型
- 基于计数的滑动窗口(默认,CircuitBreakerConfig可看出)
- 基于时间的滑动窗口
public enum SlidingWindowType {
TIME_BASED, COUNT_BASED
}
四:CircuitBreakerConfig配置
- CircuitBreakerConfig 类定义了相关配置与属性,主要有如下:
//请求调用失败的阈值,百分比。默认是50%,即服务A调用服务B,此时B调用失败即算作一个失败调用
public static final int DEFAULT_FAILURE_RATE_THRESHOLD = 50; // Percentage
//慢调用的阈值,百分比
public static final int DEFAULT_SLOW_CALL_RATE_THRESHOLD = 100; // Percentage
// 熔断器在打开状态时的持续时间。默认是60秒
public static final int DEFAULT_WAIT_DURATION_IN_OPEN_STATE = 60; // Seconds
// 熔断器在半开状态下的ring buffer大小。默认10,不超过此值就可以通过请求
public static final int DEFAULT_PERMITTED_CALLS_IN_HALF_OPEN_STATE = 10;
// 熔断器在关闭状态下的可以计算失败率的最小值,默认100
public static final int DEFAULT_MINIMUM_NUMBER_OF_CALLS = 100;
//滑动窗口大小,熔断器在关闭状态下的ring buffer大小
public static final int DEFAULT_SLIDING_WINDOW_SIZE = 100;
//慢调用的时间,即当服务A调用服务B时,B的执行时间超过了60秒就算作是慢调用
public static final int DEFAULT_SLOW_CALL_DURATION_THRESHOLD = 60; // Seconds
//滑动窗口类型,默认为基于计数的 COUNT_BASED
public static final SlidingWindowType DEFAULT_SLIDING_WINDOW_TYPE = SlidingWindowType.COUNT_BASED;
public static final boolean DEFAULT_WRITABLE_STACK_TRACE_ENABLED = true;
// 是否记录请求调用失败的断言,默认所有异常都记录
private static final Predicate<Throwable> DEFAULT_RECORD_EXCEPTION_PREDICATE = throwable -> true;
//忽略异常
private static final Predicate<Throwable> DEFAULT_IGNORE_EXCEPTION_PREDICATE = throwable -> false;
// The default exception predicate counts all exceptions as failures.
private Predicate<Throwable> recordExceptionPredicate = DEFAULT_RECORD_EXCEPTION_PREDICATE;
// The default exception predicate ignores no exceptions.
private Predicate<Throwable> ignoreExceptionPredicate = DEFAULT_IGNORE_EXCEPTION_PREDICATE;
// 默认为false,是否自动从打开到半开,当waitDurationInOpenState时间一过,是否自动从OPEN切换到HALF_OPEN
// true:waitDurationInOpenState到期后open自动变为half_open
//false,得等到再有请求后状态才会变为half_open,否则即使waitDurationInOpenState到期状态依然是open
private boolean automaticTransitionFromOpenToHalfOpenEnabled = false;
- CircuitBreakerConfig 利用Builder模式 创建了CircuitBreakerConfig实例,并提供动态设置属性的方法
/**
* 构造者模式
*/
public static class Builder {
@Nullable
private Predicate<Throwable> recordExceptionPredicate;
@Nullable
private Predicate<Throwable> ignoreExceptionPredicate;
// 请求调用失败,存储异常记录的集合
@SuppressWarnings("unchecked")
private Class<? extends Throwable>[] recordExceptions = new Class[0];
// 请求调用失败,忽略异常记录的集合
@SuppressWarnings("unchecked")
private Class<? extends Throwable>[] ignoreExceptions = new Class[0];
。。。
//创建实例并设置属性
public Builder(CircuitBreakerConfig baseConfig) {
this.waitIntervalFunctionInOpenState = baseConfig.waitIntervalFunctionInOpenState;
。。。
this.writableStackTraceEnabled = baseConfig.writableStackTraceEnabled;
}
//动态设置调用失败阈值
public Builder slidingWindow(int slidingWindowSize, int minimumNumberOfCalls,
SlidingWindowType slidingWindowType) {
.。。。
if (slidingWindowType == SlidingWindowType.COUNT_BASED) {
//如果是基于计数的滑动窗口,minimumNumberOfCalls取两者最小值
this.minimumNumberOfCalls = Math.min(minimumNumberOfCalls, slidingWindowSize);
} else {
//基于时间
this.minimumNumberOfCalls = minimumNumberOfCalls;
}
}
}
- CircuitBreakerConfig提供了两种获取Builder对象的方法:使用默认配置和自定义配置
/**
* Returns a builder to create a custom CircuitBreakerConfig.
* 1.使用默认配置创建Builder对象,得到builder对象就可以根据builder提供的方法改变配置
* @return a {@link Builder}
*/
public static Builder custom() {
return new Builder();
}
/**
* Returns a builder to create a custom CircuitBreakerConfig based on another
* CircuitBreakerConfig.
* 2.使用传入的自定义的配置baseConfig的builder对象,得到builder对象就可以根据builder提供的方法改变配置
* @return a {@link Builder}
*/
public static Builder from(CircuitBreakerConfig baseConfig) {
return new Builder(baseConfig);
}
- CircuitBreakerConfig还提供了获取配置的方法,如获取滑动窗口的类型:
public SlidingWindowType getSlidingWindowType() {
return slidingWindowType;
}
综上: 该类为创建CircuitBreakerConfig实例,设置并获取CircuitBreaker配置
yml文件配置参考
resilience4j.circuitbreaker:
configs:
default: # @CircuitBreaker的name
registerHealthIndicator: true #健康监测
ringBufferSizeInClosedState: 2 # 熔断器关闭时的缓冲区大小,不会限制线程的并发量,在熔断器发生状态转换前所有请求都会调用后端服务(缓冲区满时才会计算失败率)
ringBufferSizeInHalfOpenState: 1 # 熔断器半开时的缓冲区大小,会限制线程的并发量(只允许ringBufferSizeInHalfOpenState个并发),
# 例如缓冲区为10则每次只会允许10个请求调用后端服务
# 之所以能限制并发,CircuitBreakerStateMachine使用了AtomicInteger:this.permittedNumberOfCalls = new AtomicInteger(permittedNumberOfCallsInHalfOpenState);
waitDurationInOpenState: 10s # 熔断器从打开到半开需要的时间
failureRateThreshold: 50 # 熔断器打开的失败阈值或半开状态使用的同一个失败率阈值
slowCallDurationThreshold: 600s # 慢调用的时间,单位s
slowCallRateThreshold: 100 # 慢调用的比例阈值
eventConsumerBufferSize: 7 # 事件缓冲区大小???
minimumNumberOfCalls: 4 #默认100
# slideWindowType: COUNT_BASED # 配置滑动窗口的类型,默认为count_based
# automaticTransitionFromOpenToHalfOpenEnabled: false # 默认为false,是否自动从打开到半开,当waitDurationInOpenState时间一过,是否自动从OPEN切换到HALF_OPEN
# # true:waitDurationInOpenState到期后open自动变为half_open,
# #如果为false,得等到再有请求后状态才会变为half_open,否则即使waitDurationInOpenState到期状态依然是open
recordExceptions: # 记录的异常
- org.springframework.web.client.HttpServerErrorException
- java.io.IOException
- java.util.concurrent.TimeoutException
- org.springframework.web.client.ResourceAccessException
- org.springframework.web.client.HttpClientErrorException
ignoreExceptions: # 忽略的异常
recordFailurePredicate: #用于判断哪些异常应该算作失败纳入断路器统计,默认是Throwable类型
五:CircuitBreakerProperties读取配置
CircuitBreakerProperties 作为读取yml或properties文件中以"resilience4j.circuitbreaker"开头的配置
@ConfigurationProperties(prefix = "resilience4j.circuitbreaker")
public class CircuitBreakerProperties extends CircuitBreakerConfigurationProperties {
}
定义了CircuitBreaker切入顺序
public class CircuitBreakerConfigurationProperties extends
io.github.resilience4j.common.circuitbreaker.configuration.CircuitBreakerConfigurationProperties {
// 倒第二
private int circuitBreakerAspectOrder = Ordered.LOWEST_PRECEDENCE - 2;
。。。
将读取到的配置信息放到了Map中
public class CircuitBreakerConfigurationProperties extends CommonProperties {
private Map<String, InstanceProperties> instances = new HashMap<>();
private Map<String, InstanceProperties> configs = new HashMap<>();
。。。
}
提供了3中创建CircuitBreakerConfig的方法
public CircuitBreakerConfig createCircuitBreakerConfig(String backendName,
InstanceProperties instanceProperties,
CompositeCustomizer<CircuitBreakerConfigCustomizer> compositeCircuitBreakerCustomizer) {
// 如果配置项中有baseConfig,就去configs中找到baseConfig
if (StringUtils.isNotEmpty(instanceProperties.getBaseConfig())) {
InstanceProperties baseProperties = configs.get(instanceProperties.getBaseConfig());
if (baseProperties == null) {
throw new ConfigurationNotFoundException(instanceProperties.getBaseConfig());
}
//调用buildConfigFromBaseConfig方法创建创建CircuitBreakerConfig
// 将baseProperties属性合并到 instanceProperties
return buildConfigFromBaseConfig(instanceProperties, baseProperties,
compositeCircuitBreakerCustomizer,
backendName);
}
// 创建CircuitBreakerConfig
return buildConfig(custom(), instanceProperties, compositeCircuitBreakerCustomizer,
backendName);
}