你必须懂也可以懂的微服务系列五:服务熔断

1.系列文章

2. 前言

你必须懂也可以懂的微服务系列三:服务调用一文中介绍了服务调用实现原理,在你必须懂也可以懂的微服务系列四:服务发现与负载均衡一文中介绍了服务发现以及负载均衡实现原理。

事实上远程服务调用过程中往往会出现一些我们无法预料的异常,针对这些异常如果不采取相关的处理措施,往往会带来无法想象的灾难。举个示例:服务提供者某个接口响应慢,会造成服务调用者调用线程阻塞,当请求达到一定量,服务调用者资源被消耗完,也会出现无法对外提供服务的现象。

为了避免出现由于下游服务故障从而引起自身服务不可用的问题,作为服务调用方必须采取某种措施来进行自我保护,微服务中这种保护技术被称为熔断,也是本文将要介绍的内容

3.熔断

3.1 熔断器介绍

3.1.1 熔断器配置

在使用熔断器前需要进行相关参数配置,比如设置滑动窗口大小最小请求数等,更多相关配置可参考Create and configure a CircuitBreaker

CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
                .minimumNumberOfCalls(3)
                .slidingWindowSize(10)
                .build();
复制代码

3.1.2 熔断器注册表

熔断器注册表存储熔断器相关配置,因此可以指定熔断器配置来创建熔断器注册表

CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.of(circuitBreakerConfig);
复制代码

3.1.3 创建熔断器

熔断器注册表存储熔断器相关配置,因此可以用来创建熔断器实例

CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("hello");
复制代码

3.1.4 使用熔断器执行目标逻辑

要想实现熔断功能,就需要使用熔断器包裹目标逻辑

CheckedFunction0<String> decoratedSupplier = CircuitBreaker.decorateCheckedSupplier(
                circuitBreaker, () -> { throw new NullPointerException(); });

for (int i = 0; i < 10; i++) {
    TimeUnit.SECONDS.sleep(1);
    Try.of(decoratedSupplier).map(value -> value + " world'");
}
复制代码

3.2 熔断器实现

static <T> CheckedFunction0<T> decorateCheckedSupplier(CircuitBreaker circuitBreaker,
                                                       CheckedFunction0<T> supplier) {
    return () -> {
        // 1.获取许可,得到许可后执行业务逻辑
        circuitBreaker.acquirePermission();
        final long start = circuitBreaker.getCurrentTimestamp();
        try {
            T result = supplier.apply();
            long duration = circuitBreaker.getCurrentTimestamp() - start;
            // 2.执行成功
            circuitBreaker.onResult(duration, circuitBreaker.getTimestampUnit(), result);
            return result;
        } catch (Exception exception) {
            // Do not handle java.lang.Error
            long duration = circuitBreaker.getCurrentTimestamp() - start;
            // 3.执行失败
            circuitBreaker.onError(duration, circuitBreaker.getTimestampUnit(), exception);
            throw exception;
        }
    };
}
复制代码

3.2.1 熔断器执行失败逻辑处理

void record(long duration, TimeUnit durationUnit, Outcome outcome) {
    ++this.numberOfCalls;
    this.totalDurationInMillis += durationUnit.toMillis(duration);
    switch(outcome) {
        case SLOW_SUCCESS:
            ++this.numberOfSlowCalls;
            break;
        case SLOW_ERROR:
            ++this.numberOfSlowCalls;
            ++this.numberOfFailedCalls;
            ++this.numberOfSlowFailedCalls;
            break;
        case ERROR:
            ++this.numberOfFailedCalls;
    }

}
复制代码

每次执行失败都会对失败计数器numberOfFailedCalls 、总请求计数器numberOfCalls进行 + 1操作,调用该方法的上层方法使用了synchronized关键字,因此是线程安全的

3.2.2 计算失败率

private float getFailureRate(Snapshot snapshot) {
    int bufferedCalls = snapshot.getTotalNumberOfCalls();
    if (bufferedCalls == 0 || bufferedCalls < minimumNumberOfCalls) {
        return -1.0f;
    }
    return snapshot.getFailureRate();
}
复制代码
public float getFailureRate() {
    return this.totalNumberOfCalls == 0 ? 0.0F : (float)this.totalNumberOfFailedCalls * 100.0F / (float)this.totalNumberOfCalls;
}
复制代码

通过总失败数 / 总请求数就可以得到请求失败率,不过此处有一点需要特别注意:bufferedCalls < minimumNumberOfCalls条件,也就是当请求数达到最小请求数minimumNumberOfCalls才会计算失败率,否则不会进行计算。官网Create and configure a CircuitBreaker中也有对minimumNumberOfCalls配置进行详细说明

3.2.3 检查是否超限

private Result checkIfThresholdsExceeded(Snapshot snapshot) {
    float failureRateInPercentage = getFailureRate(snapshot);
    float slowCallsInPercentage = getSlowCallRate(snapshot);

    if (failureRateInPercentage == -1 || slowCallsInPercentage == -1) {
        return Result.BELOW_MINIMUM_CALLS_THRESHOLD;
    }
    
    if (failureRateInPercentage >= failureRateThreshold
        && slowCallsInPercentage >= slowCallRateThreshold) {
        return Result.ABOVE_THRESHOLDS;
    }
    
    // 当请求失败率超过配置的阀值(默认值50%),直接拒绝请求
    if (failureRateInPercentage >= failureRateThreshold) {
        return Result.FAILURE_RATE_ABOVE_THRESHOLDS;
    }

    if (slowCallsInPercentage >= slowCallRateThreshold) {
        return Result.SLOW_CALL_RATE_ABOVE_THRESHOLDS;
    }
    return Result.BELOW_THRESHOLDS;
}
复制代码

当请求失败率超过配置的failureRateThreshold阀值后,会对后续的请求进行熔断,直接拒绝

3.3 熔断器总结

  • 通过CircuitBreakerConfig进行熔断器配置
  • 根据CircuitBreakerConfig配置创建CircuitBreakerRegistry注册表
  • 根据CircuitBreakerRegistry注册表创建CircuitBreaker
  • 使用CircuitBreaker包裹目标业务逻辑
  • 每次请求失败对请求失败数、总请求数进行 + 1操作
  • 当请求数据超过最小请求数minimumNumberOfCalls后,计算失败率
  • 当请求失败率阀值超过配置阀值failureRateThreshold后,拒绝后续请求

3.4 融合openfeign

openfeign融合熔断器功能,当调用目标方式时会回调org.springframework.cloud.openfeign.FeignCircuitBreakerInvocationHandlerinvoke方法

3.5 组件包裹

public <T> T run(String id, Supplier<T> toRun, Function<Throwable, T> fallback, CircuitBreaker circuitBreaker,
			TimeLimiter timeLimiter) {
    Supplier<CompletionStage<T>> bulkheadCall = decorateBulkhead(id, toRun);
    Supplier<CompletionStage<T>> timeLimiterCall = timeLimiter
        .decorateCompletionStage(Executors.newSingleThreadScheduledExecutor(), bulkheadCall);
    Supplier<CompletionStage<T>> circuitBreakerCall = circuitBreaker.decorateCompletionStage(timeLimiterCall);
    try {
        return circuitBreakerCall.get().toCompletableFuture().get();
    }
    catch (Exception e) {
        System.out.println("exception " + e.getMessage());
        return fallback.apply(e);
    }
}
复制代码

3.2章节介绍了会使用熔断器包装目标业务逻辑,在SpringCloud中集成了更多的组件:TimeLimiter(调用时间限制)、Bulkhead(调用资源隔离)、CircuitBreaker(熔断)

执行顺序如下:

4.结语

只有对系统进采取相关措施进行保护,才会让系统更加健壮、持久对外提供服务

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享