Files
rikako-note/spring/Spring Cloud/CircuitBreaker/CircuitBreaker.md
2025-09-02 17:32:49 +08:00

11 KiB
Raw Blame History

CircuitBreaker

resilience4j是一个轻量级的fault tolerance library其针对函数式编程进行设计。resilence4j提供了更高阶的函数decorator)来对function interface, lambda expression, method reference等内容进行增强。

decorators包含如下分类

  • CircuitBreaker
  • Rate Limiter
  • Retry
  • Bulkhead

对于任何function interface, lambda expression, method reference都可以使用多个decorators进行装饰。

Introduction

在如下示例中会展示如何通过CircuitBreaker和Retry来对lambda expression进行装饰令lambda在发生异常时最多重试3次。

可以针对多次retry之间的interval进行配置也支持自定义的backoff algorithm。

// Create a CircuitBreaker with default configuration
CircuitBreaker circuitBreaker = CircuitBreaker
  .ofDefaults("backendService");

// Create a Retry with default configuration
// 3 retry attempts and a fixed time interval between retries of 500ms
Retry retry = Retry
  .ofDefaults("backendService");

// Create a Bulkhead with default configuration
Bulkhead bulkhead = Bulkhead
  .ofDefaults("backendService");

Supplier<String> supplier = () -> backendService
  .doSomething(param1, param2)

// Decorate your call to backendService.doSomething() 
// with a Bulkhead, CircuitBreaker and Retry
// **note: you will need the resilience4j-all dependency for this
Supplier<String> decoratedSupplier = Decorators.ofSupplier(supplier)
  .withCircuitBreaker(circuitBreaker)
  .withBulkhead(bulkhead)
  .withRetry(retry)  
  .decorate();

// When you don't want to decorate your lambda expression,
// but just execute it and protect the call by a CircuitBreaker.
String result = circuitBreaker
  .executeSupplier(backendService::doSomething);

// You can also run the supplier asynchronously in a ThreadPoolBulkhead
 ThreadPoolBulkhead threadPoolBulkhead = ThreadPoolBulkhead
  .ofDefaults("backendService");

// The Scheduler is needed to schedule a timeout 
// on a non-blocking CompletableFuture
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
TimeLimiter timeLimiter = TimeLimiter.of(Duration.ofSeconds(1));

CompletableFuture<String> future = Decorators.ofSupplier(supplier)
    .withThreadPoolBulkhead(threadPoolBulkhead)
    .withTimeLimiter(timeLimiter, scheduledExecutorService)
    .withCircuitBreaker(circuitBreaker)
    .withFallback(asList(TimeoutException.class, 
                         CallNotPermittedException.class, 
                         BulkheadFullException.class),  
                  throwable -> "Hello from Recovery")
    .get().toCompletableFuture();

maven

resilence4j需要jdk17及以上如果使用maven可以按照如下方式来引入

引入所有包的方式如下

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-all</artifactId>
    <version>${resilience4jVersion}</version>
</dependency>

按需引入方式如下

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-circuitbreaker</artifactId>
    <version>${resilience4jVersion}</version>
</dependency>
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-ratelimiter</artifactId>
    <version>${resilience4jVersion}</version>
</dependency>
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-retry</artifactId>
    <version>${resilience4jVersion}</version>
</dependency>
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-bulkhead</artifactId>
    <version>${resilience4jVersion}</version>
</dependency>
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-cache</artifactId>
    <version>${resilience4jVersion}</version>
</dependency>
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-timelimiter</artifactId>
    <version>${resilience4jVersion}</version>
</dependency>

CircuitBreaker

State Machine

CircuitBreaker通过有限状态机实现其拥有如下状态

  • CLOSED
  • OPEN
  • HALF_OPEN
  • METRICS_ONLY
  • DISABLED
  • FORCED_OPEN

其中,前三个状态为正常状态,后三个状态为特殊状态。

上述circuitbreaker状态转换逻辑如下所示

  • 处于CLOSED状态时,如果实际接口的失败率超过上限后,会从CLOSED状态转换为OPEN状态
  • 处于OPEN状态下经过一段时间后,会从OPEN状态转换为HALF_OPEN状态
  • 处于HALF_OPEN状态下,如果失败率小于上限,会从HALF_OPEN状态重新变为CLOSED状态
  • 如果HALF_OPEN状态下,失败率仍然超过上限,则会从HALF_OPEN状态重新变为OPEN状态

Sliding Window

CircuitBreaker会使用滑动窗口来存储和聚合调用结果。在使用CircuitBreaker时可以选择count-based的滑动窗口还是time-based滑动窗口。

  • count-basedcount-based滑动窗口会对最近N次调用的结果进行聚合
  • time-basedtime-based滑动窗口将会对最近N秒的调用结果进行聚合

Count-based sliding window

count-based sliding window是通过循环数组来实现的循环数组中包含了n个measurements。如果count window的大小为10那么circular array一直都会有10个measurements。

count-based的滑动窗口实现会total aggregation结果进行更新,更新逻辑如下:

  • 当一个新的调用返回结果后其结果将会被记录并且total aggregation也会被更新将新调用的结果加到total aggregation中
  • 发生新调用时循环数组中最老oldest的measurement将会被淘汰并且measurement也会从total aggregation中被减去bucket也会被重置bucket即measurementbucket被重置即代表oldest measurement会被重置

对于聚合结果的检查的开销是O(1)的,因为其是pre-aggregated并且和window size无关。

Time-based sliding window

Time-based sliding window其也是通过循环数组实现数组中含有N个partial aggregationbucket

如果time window大小是10秒那么circular array一直都会有10的buckets。每个bucket都对应了一个epoch secondbucket会对该epoch second内发生的调用结果进行聚合。Partial aggregation)。

在循环数组中head buket中存储了当前epoch second中发生的调用结果而其他的partial aggregation则存储的是之前second发生的调用结果。在Time-based的滑动窗口实现中并不会像Count-based那样独立的存储调用结果,而是增量的对partial aggregation进行更新。

除了更新Partial aggregationtime-based滑动窗口还会在新请求结果返回时total aggregation进行更新。当oldest bucket被淘汰时该bucket的partial aggregation也会从total aggregation中被减去并且bucket也会被重置。

检查聚合结果的开销也是O(1)Time-based滑动窗口也是pre-aggregated的。

partial aggregation中包含了3个integer用于记录如下信息

  • failed calls次数
  • slow calls次数
  • 总共的call次数

除此之外partial aggregation中还会包含一个long用于存储所有请求的总耗时

Failure rate and slow call rate thresholds

Failure rate & exception list

当failure rate大于等于配置的threshold时CircuitBreaker的状态将会从CLOSED变为OPEN

默认情况下所有的抛出的异常都会被统计为failure在使用时也可以指定一个exception list在exception list中的异常才会被统计为failure而不在exception list中的异常会被视为success。除此之外还可以对异常进行ignored,被忽视的异常既不会被统计为success也不会被统计为failure

Slow call rate

当slow call rate大于或等于配置的threshold时CircuitBreaker的状态也会从CLOSED变为OPEN。通过slow call rate可以降低外部系统的负载。

只有当记录的call数量达到最小数量时failure rate和slow call rate才能被计算。例如minimum number of required calls为10只有当被记录的calls次数达到10时failure rate和slow call rate才能被计算。如果当前只记录了9个calls即使9次调用全部都失败circuitbreaker也不会变为open状态。

CircuitBreaker in OPEN/HALF_OPEN state

circuitbreaker在OPEN状态时,会拒绝所有的调用,并且抛出CallNotPermittedException。在等待一段时间后,CircuitBreaker将会从OPEN状态转为HALF_OPEN状态,并允许一个configurable number数量的请求进行实际调用从而检测是否backend已经恢复并且可以再次访问。

处于HALF_OPEN状态的circuitbreaker假设permittedNumberOfCalls的数量为10此时存在20个调用那么前10个调用都能正常调用而后10个调用将会被拒绝并且抛出CallNotPermittedException

HALF_OPEN状态下如果failure rate或是slow call rate大于等于配置的threshold那么circuitbreaker状态将会转为OPEN。如果failure rate和slow call rate小于threshold那么circuitbreaker状态将变为CLOSED。

Special States

CircuitBreaker支持3个特殊状态

  • METRICS_ONLY:处于该状态时,其行为如下
    • 所有circuit breaker events都会正常生成除了state transition外并且metrics会正常记录
    • 该状态和CLOSED状态类似但是circuitbreaker在threshold达到时不会切换为OPEN状态
  • DISABLED
    • 没有CircuitBreakerEvent会被产生metrics也不会被记录
    • 会允许所有的访问
  • FORCED_OPEN:
    • 没有CircuitBreakerEvent会被产生metrics也不会被记录
    • 会拒绝所有的访问

退出这些特殊状态的方式如下:

  • 触发state transition
  • 对CircuitBreaker执行reset

thread-safe

CircuitBreaker线程安全但是CircuitBreaker并不会对function call进行串行化故而在使用CircuitBreaker时function call可能会并行执行

对于Closed状态的CircuitBreaker而言如果20个线程同时对cirbuitbreaker进行访问那么所有的方法调用都能同时并行执行即使滑动窗口的大小为15小于并行数量。滑动窗口的大小不会对方法的并行程度造成影响

如果想要对并行程度做出限制,可以使用Bulkhead