断路器CircuitBreaker

SpringCloud断路器功能生效的条件是:容器中有CircuitBreakerFactory类型的bean,如果没有,则注入默认的DefaultTargeter;如果有,则注入FeignCurcuitBreakerTargeter。

class FeignAutoConfiguration{
	@Bean
	@ConditionalOnMissingBean(CircuitBreakerFactory.class)
	public Targeter defaultFeignTargeter() {
		return new DefaultTargeter();
	}
	@Bean
	@ConditionalOnMissingBean
	@ConditionalOnBean(CircuitBreakerFactory.class)
	public Targeter circuitBreakerFeignTargeter(CircuitBreakerFactory circuitBreakerFactory,
			@Value("${feign.circuitbreaker.group.enabled:false}") boolean circuitBreakerGroupEnabled,
			CircuitBreakerNameResolver circuitBreakerNameResolver) {
		return new FeignCircuitBreakerTargeter(circuitBreakerFactory, circuitBreakerGroupEnabled,
				circuitBreakerNameResolver);
	}

}

FeignCircuitBreakerTargeter通过FeignCircuitBreaker.Builder创建Feign接口代理,拦截器器是FeignCircuitBreakerInvocationHandler,它通过CircuitBreakerFactory创建CircuitBreaker,然后执行CircuitBreaker.run执行断路器逻辑和业务逻辑

class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
	@Override
	public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
	...
			CircuitBreaker circuitBreaker = circuitBreakerGroupEnabled ? factory.create(circuitName, feignClientName)
				: factory.create(circuitName);
			return circuitBreaker.run(supplier, fallbackFunction);
	}
}

Resilience4jCircuitBreaker

是CircuitBreaker的Resilience4j实现类

  1. 执行业务逻辑之前,尝试获取准许,CircuitBreaker.acquirePermission()
  2. 执行业务逻辑之后,如果没有异常,执行CircuitBreaker.onResult;如果有异常,执行CircuitBreaker.onError,并把执行时间、结果或异常传入
//注意,这里的CircuitBreaker 是Resilience4j包(io.github.resilience4j.circuitbreaker.CircuitBreaker)下的断路器,
//而不是springcloud的断路器,org.springframework.cloud.client.circuitbreaker.CircuitBreaker
static <T> Supplier<T> decorateSupplier(CircuitBreaker circuitBreaker, Supplier<T> supplier) {
    return () -> {
        circuitBreaker.acquirePermission();
        final long start = circuitBreaker.getCurrentTimestamp();
        try {
            T result = supplier.get();
            long duration = circuitBreaker.getCurrentTimestamp() - start;
            circuitBreaker.onResult(duration, circuitBreaker.getTimestampUnit(), result);
            return result;
        } catch (Exception exception) {
            // Do not handle java.lang.Error
            long duration = circuitBreaker.getCurrentTimestamp() - start;
            circuitBreaker.onError(duration, circuitBreaker.getTimestampUnit(), exception);
            throw exception;
        }
    };
}

CircuitBreakerStateMachine

是CircuitBreaker的实现类,断路器同一时刻只会处于一种状态,状态之间可以相互转换

状态:

  • 关闭:所有调用都能执行,执行后根据结果是否异常和执行时间计算慢调用比例和失败比例,当调用的总次数达到一定值(默认100),并且其中一个比例达到阈值(慢调用100%,失败50%)时,断路器转变成开启状态
  • 开启:所有调用都不能执行,抛CallNotPermittedException(默认Throwable.writableStackTrace=true,会打印异常栈),执行降级Fallback逻辑。开启后,等待一段时间自动变成半开状态,等待时间默认是1分钟,等待策略有固定等待策略、范围随机、指数增长
  • 半开:用于测试调用的执行情况,允许通过少量调用请求(默认10),统计这些请求的慢调用比例和失败比例,当比例都小于阈值时,关闭;否则开启
  • 禁用(disable):断路器不起作用
  • 强制打开:所有调用都不能执行
  • 只度量:所有调用都能执行,超出门限时触发事件,并且只触发一次
public final class CircuitBreakerStateMachine implements CircuitBreaker {
//断路器状态,状态切换通过原子类的方法实现,java.util.concurrent.atomic.AtomicReference#getAndUpdate
	private final AtomicReference<CircuitBreakerState> stateReference;
	@Override
    public void acquirePermission() {
        //调用前执行,执行状态的获取方法
            stateReference.get().acquirePermission(); 
    }
        @Override
    public void onSuccess(long duration, TimeUnit durationUnit) { 
        stateReference.get().onSuccess(duration, durationUnit);
    }
    @Override
    public void onError(long duration, TimeUnit durationUnit, Throwable throwable) {
        stateReference.get().onError(duration, durationUnit, throwable);
    }
    @Override
    public void transitionToClosedState() {
    //状态切换时,会重新创建一个新的状态
        stateTransition(CLOSED, currentState -> new ClosedState());
    }
}

状态的公共接口

   private interface CircuitBreakerState {
        boolean tryAcquirePermission();
        void acquirePermission();
        void releasePermission();
        void onError(long duration, TimeUnit durationUnit, Throwable throwable);
        void onSuccess(long duration, TimeUnit durationUnit);
        //实际上是转变为开启状态的次数,用来计算开启到半开的等待时间
        int attempts();
        //负责统计调用次数和比例,每个状态对象对应一个
        CircuitBreakerMetrics getMetrics();
    }

FixedSizeSlidingWindowMetrics

固定请求数滑动窗口,统计最近n个请求的指标(慢调用次数,失败次数,总次数,总调用时间)。通过一个环形数组,数组每个元素记录了最近n个请求的指标,维护最近一次调用对应的索引,每次调用时,索引值向后移动一位,将总的指标减去这个元素的指标,重置移动后对应的元素的指标,再将最新的调用指标记录在这个元素上。

public class FixedSizeSlidingWindowMetrics implements Metrics {
	//滑动窗口大小
    private final int windowSize;
    //最近n次调用的总指标
    private final TotalAggregation totalAggregation;
    //滑动窗口,环形数组
    private final Measurement[] measurements;
    //最近一次请求的滑动窗口位置
    int headIndex;
    @Override
    public synchronized Snapshot record(long duration, TimeUnit durationUnit, Outcome outcome) {
    //在总指标中记录
        totalAggregation.record(duration, durationUnit, outcome);
        //在滑动窗口中记录,为了删除
        moveWindowByOne().record(duration, durationUnit, outcome);
        return new SnapshotImpl(totalAggregation);
    }
    private Measurement moveWindowByOne() {
        moveHeadIndexByOne();
        Measurement latestMeasurement = getLatestMeasurement();
        //删除过期的指标
        totalAggregation.removeBucket(latestMeasurement);
        //重置为0
        latestMeasurement.reset();
        return latestMeasurement;
    }
}

SlidingTimeWindowMetrics

滑动时间窗口,记录的是最近n秒的指标。和FixedSizeSlidingWindowMetrics 类似,也是通过环形数组实现,但数组的每个元素代表的是1秒内的指标,可能对应多次调用,也可能0次。每个元素对应一个秒级的时间戳,移动一格代表时间+1s,当有新的请求时,移动滑动窗口的结束位置直到请求的时间戳,从总指标中删除移动过的元素的指标,重置指标,重设元素的时间戳

private PartialAggregation moveWindowToCurrentEpochSecond(
        PartialAggregation latestPartialAggregation) {
        long currentEpochSecond = clock.instant().getEpochSecond();
        //最近的位置表示滑动窗口时间的最大值,如果当前时刻大于它,则需要移动
        long differenceInSeconds = currentEpochSecond - latestPartialAggregation.getEpochSecond();
        if (differenceInSeconds == 0) {
            return latestPartialAggregation;
        }
        //整个滑动窗口都过期了
        long secondsToMoveTheWindow = Math.min(differenceInSeconds, timeWindowSizeInSeconds);
        PartialAggregation currentPartialAggregation;
        do {
            secondsToMoveTheWindow--;
            //移动滑动窗口的结束位置
            moveHeadIndexByOne();
            currentPartialAggregation = getLatestPartialAggregation();
            //结束位置之后的指标已经过期了
            totalAggregation.removeBucket(currentPartialAggregation);
            currentPartialAggregation.reset(currentEpochSecond - secondsToMoveTheWindow);
        } while (secondsToMoveTheWindow > 0);
        return currentPartialAggregation;
    }

滑动时间窗口的时间复杂度是O(n),因为最坏情况下整个窗口都需要重置;固定调用次数滑动窗口的时间复杂度是O(1),是默认的实现,因为每次调用只会移动一次窗口

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值