CircuitBreaker Resilience4J 介绍

最近接触到了CircuitBreaker Resilience4J ,网上查了查资料,这里整合记录一下,供大家学习和参考

目录

前言

1、熔断器出现背景

        ​​​​​​​ 2、支持断路器实现有以下几种

3、Circuit Breaker原理

4、概念说明

限流 — RateLimiter    

重试 — Retry   

超时 — Timeout   

一、CircuitBreaker断路器

二、基于数量(count-based)的滑动窗口

三、基于时间(time-based)的滑动窗口

四、故障率和慢调用阀值

五、创建一个CircuitBreakerRegistry实例

六、创建并配置CircuitBreaker断路器

六、修饰并执行函数式接口

七、消费发布的RegistryEvents时间

八、消费发布的CircuitBreakerEvents事件

九、覆盖RegistryStore

十、CircuitBreaker配置案例

十、resilience4j链接

参考:


前言

        在分布式环境中,我们的应用可能会面临着各种各样的可恢复的异常(比如超时,网络环境异常),此时我们可以利用不断重试的方式来从异常中恢复(Retry Pattern),使整个集群正常运行。

        然而,也有一些异常比较顽固,突然发生,无法预测,而且很难恢复,并且还会导致级联失败(举个例子,假设一个服务集群的负载非常高,如果这时候集群的一部分挂掉了,还占了很大一部分资源,整个集群都有可能遭殃)。如果我们这时还是不断进行重试的话,结果大多都是失败的。因此,此时我们的应用需要立即进入失败状态(fast-fail),并采取合适的方法进行恢复。

        Circuit Breaker Pattern(熔断器模式)就是这样的一种设计思想。它可以防止一个应用不断地去尝试一个很可能失败的操作。一个Circuit Breaker相当于一个代理,用于监测某个操作对应的失败比率(fail / fail + success)。它会根据得到的数据来决定是否允许执行此操作,或者是立即抛出异常。

1、熔断器出现背景

       在微服务的开发中,服务模块化拆分后,服务之间的调用关系错综复杂,当出现服务调用链路过长时,服务提供者出现网络超时或者故障时,过多的调用堆积会导致服务调用者占用资源过多,最终出现服务调用者也不能提供正常的服务。这时候就需要一种保护机制,通过实现调用熔断,以达到保护服务调用者的目的。

        如下图所示:A服务调用B服务,B服务调用C服务,当C服务出现故障或者超时时,大量的A服务调用请求导致B服务大量超时,最终导致B服务出现故障,一致到最终的调用端。

2、支持断路器实现有以下几种

  • Resilience4J (支持)
  • Netfix Hystrix (Spring Cloud官方已经不予支持)
  • Sentinel
  • Spring Retry

3、Circuit Breaker原理

我们可以用状态机来实现 Circuit Breaker,它有以下三种状态:

  • 关闭(Closed):默认情况下Circuit Breaker是关闭的,此时允许操作执行。Circuit Breaker内部记录着最近失败的次数,如果对应的操作执行失败,次数就会续一次。如果在某个时间段内,失败次数(或者失败比率)达到阈值,Circuit Breaker会转换到开启(Open)状态。在开启状态中,Circuit Breaker会启用一个超时计时器,设这个计时器的目的是给集群相应的时间来恢复故障。当计时器时间到的时候,Circuit Breaker会转换到半开启(Half-Open)状态。
  • 开启(Open):在此状态下,执行对应的操作将会立即失败并且立即抛出异常。
  • 半开启(Half-Open):在此状态下,Circuit Breaker 会允许执行一定数量的操作。如果所有操作全部成功,Circuit Breaker就会假定故障已经恢复,它就会转换到关闭状态,并且重置失败次数。如果其中 任意一次 操作失败了,Circuit Breaker就会认为故障仍然存在,所以它会转换到开启状态并再次开启计时器(再给系统一些时间使其从失败中恢复)。

Circuit Breaker 的状态图:

4、概念说明

限流 — RateLimiter    

        流量控制是确保服务的高可用性和可靠性的重要技术。流控的场景,服务 A 依赖服务 B,服务 A 有 3 个实例,服务 B 会为了接收到请求做大量的 CPU / IO 密集工作,因此服务 B 在给定的时间范围内设置可以处理的最大请求数的限制。

设置流控后

流控和断路器的区别

  • 流控:速率限制器通过控制吞吐量来帮助保护服务器免于过载。
  • 断路器:当目标服务器出现故障/无响应时,Circuit Breaker 有助于保持客户端的安全和正常运行。

相关配置:请参考附录 RateLimiter 配置

重试 — Retry   

        微服务体系中,多个服务互相依赖,当被依赖的服务出现问题而无法按预期响应时,就会级联到下游服务,导致不良的用户体验。

        同样,在微服务体系中,一个服务会有多个实例,如果其中一个实例可能有问题,并且无法正确响应我们的请求,则如果我们重试该请求,则负载均衡器可以将请求发送到运行状况良好的节点并正确获得响应。通过重试,有更多机会获得正确的响应。

相关配置:请参考附录 Retry 配置

超时 — Timeout   

        在微服务体系中,服务间相互依赖,例如:A—>B—>C—>D,可能由于某些网络原因,导致被依赖服务 D 无法按预期响应,这种缓慢会导致下游服务一直到服务 A,并且阻塞单个服务中的线程。由于这不是很常见的问题,在设计时需要设置超时来应对服务缓慢/不可用性问题。

  • 即使依赖服务不可用,也可以使核心服务始终工作

  • 避免无限期的等待

  • 避免阻塞任何线程

  • 使用一些缓存的响应来处理与网络相关的问题并使系统保持运行状态

相关配置:请参考附录 Timeout 配置

一、CircuitBreaker断路器

        CircuitBreaker断路器通过具有三种正常状态的有限状态机实现:CLOSED、OPEN、HALF_OPEN和两种特殊的状态DISABLED和FORCED_OPEN;

 

        CircuitBreaker断路器使用滑动窗口存储和汇总调用结果,你可以在基于时间(time-based)的滑动窗口和基于计数(count-based)的滑动窗口之间做选择。基于计数的滑动窗口会汇总最后N次调用的结果,基于时间的滑动窗口会汇总最后N秒的调用结果。

        resilience4j的CircuitBreaker(以下称为CircuitBreaker)基于状态机实现,共有6种状态,并在这6种状态之间转换。

  • CLOSED: 关闭状态,代表正常情况下的状态,允许所有请求通过,能通过状态转换为OPEN
  • HALF_OPEN: 半开状态,即允许一部分请求通过,能通过状态转换为CLOSED和OPEN
  • OPEN: 熔断状态,即不允许请求通过,能通过状态转为为HALF_OPEN
  • DISABLED: 禁用状态,即允许所有请求通过,出现失败率达到给定的阈值也不会熔断,不会发生状态转换。
  • METRICS_ONLY: 和DISABLED状态一样,也允许所有请求通过不会发生熔断,但是会记录失败率等信息,不会发生状态转换。
  • FORCED_OPEN: 与DISABLED状态正好相反,启用CircuitBreaker,但是不允许任何请求通过,不会发生状态转换。

介绍主要的3种状态: closed、open和half_open 

  • closed -> open : 关闭状态到熔断状态, 当失败的调用率(比如超时、异常等)默认50%,达到一定的阈值服务转为open状态,在open状态下,所有的请求都被拦截。
  • open-> half_open: 当经过一定的时间后,CircubitBreaker中默认为60s服务调用者允许一定的请求到达服务提供者。
  • half_open -> open: 当half_open状态的调用失败率超过给定的阈值,转为open状态
  • half_open -> closed: 失败率低于给定的阈值则默认转换为closed状态

二、基于数量(count-based)的滑动窗口

        基于计数(count-based)的滑动窗口由N个状态组成的圆形数组组成,如果窗口计数值是10,说明圆形数组计数为10次。滑动窗口以增量的方式更新汇总计数,汇总计数会在新的调用结果返回后更新。当旧的计数被逐出时,会从总的计数中减去计数值,并重置存储桶。

三、基于时间(time-based)的滑动窗口

        ​ 基于时间(time-based)的滑动窗口由N个状态组成的圆形数组组成,如果窗口时间是10秒,说明圆形数组统计时间为10秒。每个bucket汇总在某一秒内发生的所有调用的结果。循环数组的头bucket存储第二个轮回的返回结果,

        滑动窗口不单独存储调用结果,而是增量更新部分聚合(bucket)和总聚合(bucket)。

        当记录新的调用结果时,总聚合将以增量的方式更新。逐出最旧的存储桶时,将从总聚合中减去该存储桶的部分总聚合,并重置该存储桶。

四、故障率和慢调用阀值

​         当故障率大于等于配置的阀值时CircuitBreaker断路器的状态会由CLOSED转为OPEN。例如:记录的故障率大于50%时,默认所有的异常都视为故障,你可以定义一个可以被视为故障的异常列表。其它所有的异常都会计为成功,除非被忽略。异常也可以被忽略,因此其即可以不计入异常也不计入成功。

        ​ 当慢调用的百分比大于等于配置的阀值时CircuitBreaker断路器会由CLOSED转为OPEN。例如:当调用记录的50%耗时超过5秒,这有助于在外部系统实际无响应之前减少其负载。

        ​ 如果记录了最少的调用次数,故障率和慢调用只可以通过计算判定。例如:如果配置最小调用次数是10,那么最少要记录10次,在故障率计算出来之前,如果评估了9次调用,即使所有9次调用均失败,断路器状态也不会转为OPEN。

        ​ 当CircuitBreaker断路器状态为OPEN时将会拒绝调用并抛出CallNotPermittedException异常,等待一段时间后,CircuitBreaker断路器状态由OPEN转为HALF_OPEN并允许进行可配置的调用次数,以查看后端是否仍然不可用或已恢复可用。在所有的允许调用完成后,更多的调用将会被拒绝并抛出CallPermittedException异常。

        ​ 如果故障率或慢调用率大于等于配置的阀值,断路器的状态变为OPEN。如果故障率和慢调用率低于配置的阀值,断路器的状态变为CLOSED。

        ​ CircuitBreaker断路器支持另外两种特殊的状态,DISABLED(一直允许访问)和FORCED_OPEN(一直拒绝访问)。在这两种状态下,不会产生断路器事件(状态转换除外),也不会记录任何指标。退出这些状态的唯一方法是触发状态转换或重置断路器。

CircuitBreaker断路器是线程安全的,如下所示:

  • CircuitBreaker断路器的状态存储在AtomicReference;
  • CircuitBreaker断路器使用原子操作更新状态;
  • 通过滑动窗口同步记录调用次数和读取快照;

        这意味着原子性应该得到保证,一个时间点只能有一个线程更新状态或滑动窗口;

        然而CircuitBreaker断路器不是同步调用函数,这也就意味着函数调用不是关键部分;否则,断路器将会遇到巨大的性能损失和瓶颈,慢的函数调用会对整体性能/吞吐量造成巨大的负面影响;

        如果20个并发线程请求执行一个函数的权限,并且CircuitBreaker断路器的状态为CLOSED,则允许所有的线程调用该函数。即使滑动窗口的大小是15,滑动窗口并不意味着只允许同时运行15个调用,如果要限制并发线程的数量,请使用Bulkhead,你可以把Bulkhead和CircuitBreaker断路器结合起来。

一个线程示例:

三个线程示例:

五、创建一个CircuitBreakerRegistry实例

        Resilience4j附带了一个基于ConcurrentHashMap内存的CircuitBreakerRegistry,它提供了线程安全和原子性保证,你可以使用CircuitBreakerRegistry来管理(创建和检索)断路器实例。你可以为所有断路器实例创建全局默认CircuitBreakerConfig的CircuitBreakerRegistry实例;

CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults();

六、创建并配置CircuitBreaker断路器

        你可以提供自定义的全局配置CircuitBreakerConfig,要创建自定义全局CircuitBreakerConfig,可以使用CircuitBreakerConfig builder生成器,可以使用builder配置如下属性:

Config propertyDefault ValueDescription
failureRateThreshold50

Configures the failure rate threshold in percentage.

When the failure rate is equal or greater than the threshold the CircuitBreaker transitions to open and starts short-circuiting calls.

配置故障率阀值百分比。当故障率大于等于CircuitBreaker断路器阀值状态转为OPEN,并且开启短路调用状态;

slowCallRateThreshold100

Configures a threshold in percentage. The CircuitBreaker considers a call as slow when the call duration is greater than slowCallDurationThreshold

When the percentage of slow calls is equal or greater the threshold, the CircuitBreaker transitions to open and starts short-circuiting calls.

配置慢查询阀值百分比,CircuitBreaker断路器将调用持续时间大于等于slowCallDurationThreshold阀值的调用视为慢查询,当慢查询百分比大于等于阀值,CircuitBreaker断路器转为OPEN状态,并且开启短路调用状态

slowCallDurationThreshold60000 [ms]

Configures the duration threshold above which calls are considered as slow and increase the rate of slow calls.

配置调用持续时间阀值,超过阀值的调用将被视为慢调用,并增加慢查询百分比

permittedNumberOfCalls
InHalfOpenState
10

Configures the number of permitted calls when the CircuitBreaker is half open.

当CircuitBreaker断路器处于Half-Open(半开)状态时允许的正常调用次数。

maxWaitDurationInHalfOpenState0 [ms]

Configures a maximum wait duration which controls the longest amount of time a CircuitBreaker could stay in Half Open state, before it switches to open.
Value 0 means Circuit Breaker would wait infinitely in HalfOpen State until all permitted calls have been completed.

配置最大等待持续时间,以控制断路器在切换至OPEN状态之前保持HALF OPEN状态的最长时间。值为0标识断路器将在半开状态下无限等待,直到所有允许的调用完成。

slidingWindowTypeCOUNT_BASED

Configures the type of the sliding window which is used to record the outcome of calls when the CircuitBreaker is closed.
Sliding window can either be count-based or time-based.

If the sliding window is COUNT_BASED, the last slidingWindowSize calls are recorded and aggregated.
If the sliding window is TIME_BASED, the calls of the last slidingWindowSize seconds recorded and aggregated.

配置滑动窗口的类型,当断路器closed时用于记录调用结果;滑动窗口可以是count-based或time-based; 如果滑动窗口是COUNT_BASED,最后的slidingWindowSize将会以次数为单位计算和聚合次数。如果滑动窗口是TIME_BASED的,最后的slidingWindowSize将以秒为单位记录和聚合;

slidingWindowSize100

Configures the size of the sliding window which is used to record the outcome of calls when the CircuitBreaker is closed.

配置用于记录CircuitBreaker关闭时的调用次数(时间)

minimumNumberOfCalls100

Configures the minimum number of calls which are required (per sliding window period) before the CircuitBreaker can calculate the error rate or slow call rate.
For example, if minimumNumberOfCalls is 10, then at least 10 calls must be recorded, before the failure rate can be calculated.
If only 9 calls have been recorded the CircuitBreaker will not transition to open even if all 9 calls have failed.

配置CircuitBreaker断路器计算故障率或慢查询率之前的最小调用次数(单个滑动窗口期);例如:如果minimumNumberOfCalls值为10,如果CircuitBreaker断路器记录了9次调用将不会转为OPEN状态,即使9次都失败

waitDurationInOpenState60000 [ms]

The time that the CircuitBreaker should wait before transitioning from open to half-open.

断路器由打开状态转为关闭状态需要的时间

automaticTransition
FromOpenToHalfOpenEnabled
false

If set to true it means that the CircuitBreaker will automatically transition from open to half-open state and no call is needed to trigger the transition. A thread is created to monitor all the instances of CircuitBreakers to transition them to HALF_OPEN once waitDurationInOpenState passes. Whereas, if set to false the transition to HALF_OPEN only happens if a call is made, even after waitDurationInOpenState is passed. The advantage here is no thread monitors the state of all CircuitBreakers.

如果设置为true,则意味着断路器将自动从OPEN状态转换为HALF_OPEN状态,无需调用即可触发转换。创建一个线程来监视断路器的所有实例,以便在waitDurationInOpenState通过后将其转换为HALF_OPEN状态。然而如果设置为false,则只有在发出调用时才会转换为HALF_OPEN,即使waitDurationInOpenState被设置之后也是如此,优点是没有线程监视所有断路器的状态。

recordExceptionsempty

A list of exceptions that are recorded as a failure and thus increase the failure rate.
Any exception matching or inheriting from one of the list counts as a failure, unless explicitly ignored via ignoreExceptions.
If you specify a list of exceptions, all other exceptions count as a success, unless they are explicitly ignored by ignoreExceptions.

记录为错误的异常列表用于增加故障率,任何匹配到的异常或者其子类都会被当做失败。除非通过ignoreExceptions忽略掉的异常,如果你指定了一个异常列表,所有其它的异常都会被计算为成功,除非他们被ignoreExceptions忽略。

ignoreExceptionsempty

A list of exceptions that are ignored and neither count as a failure nor success.
Any exception matching or inheriting from one of the list will not count as a failure nor success, even if the exceptions is part of recordExceptions.

一个指定被忽略的异常列表,既不会被计入成功也不会计入失败,任何匹配的异常或者异常的子类都不会计入成功或者失败,即使异常时recordExceptions

recordFailurePredicatethrowable->true,默认所有的异常都被记录为失败。

A custom Predicate which evaluates if an exception should be recorded as a failure.
The Predicate must return true if the exception should count as a failure. The Predicate must return false, if the exception
should count as a success, unless the exception is explicitly ignored by ignoreExceptions.

一个自定义断言,用于计算异常是否应该记录为失败。如果异常要记录为失败,则必须返回true;如果异常要记录为成功,则必须返回false;除非ignoreExceptions明确忽略该异常。

ignoreExceptionPredicatethrowable->false,默认没有异常会被忽略

A custom Predicate which evaluates if an exception should be ignored and neither count as a failure nor success.
The Predicate must return true if the exception should be ignored.
The Predicate must return false, if the exception should count as a failure.

一个自定义断言,用于判定是否被忽略,即不被视为失败也不被视为成功。如果异常要被忽略,则必须返回true;如果异常要被视为失败,则必须返回false。

// Create a custom configuration for a CircuitBreaker
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
  .failureRateThreshold(50)
  .slowCallRateThreshold(50)
  .waitDurationInOpenState(Duration.ofMillis(1000))
  .slowCallDurationThreshold(Duration.ofSeconds(2))
  .permittedNumberOfCallsInHalfOpenState(3)
  .minimumNumberOfCalls(10)
  .slidingWindowType(SlidingWindowType.TIME_BASED)
  .slidingWindowSize(5)
  .recordException(e -> INTERNAL_SERVER_ERROR
                 .equals(getResponse().getStatus()))
  .recordExceptions(IOException.class, TimeoutException.class)
  .ignoreExceptions(BusinessException.class, OtherBusinessException.class)
  .build();

// Create a CircuitBreakerRegistry with a custom global configuration
CircuitBreakerRegistry circuitBreakerRegistry = 
  CircuitBreakerRegistry.of(circuitBreakerConfig);

// Get or create a CircuitBreaker from the CircuitBreakerRegistry 
// with the global default configuration
CircuitBreaker circuitBreakerWithDefaultConfig = 
  circuitBreakerRegistry.circuitBreaker("name1");

// Get or create a CircuitBreaker from the CircuitBreakerRegistry 
// with a custom configuration
CircuitBreaker circuitBreakerWithCustomConfig = circuitBreakerRegistry
  .circuitBreaker("name2", circuitBreakerConfig);


你可以添加由多个断路器共享的实例配置:

CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
  .failureRateThreshold(70)
  .build();

circuitBreakerRegistry.addConfiguration("someSharedConfig", config);

CircuitBreaker circuitBreaker = circuitBreakerRegistry
  .circuitBreaker("name", "someSharedConfig");


可以覆盖配置:

CircuitBreakerConfig defaultConfig = circuitBreakerRegistry
   .getDefaultConfig();

CircuitBreakerConfig overwrittenConfig = CircuitBreakerConfig
  .from(defaultConfig)
  .waitDurationInOpenState(Duration.ofSeconds(20))
  .build();

如果你不想使用CircuitBreakerRegistry管理CircuitBreaker实例,你可以直接创建实例对象:

// Create a custom configuration for a CircuitBreaker
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
  .recordExceptions(IOException.class, TimeoutException.class)
  .ignoreExceptions(BusinessException.class, OtherBusinessException.class)
  .build();

CircuitBreaker customCircuitBreaker = CircuitBreaker
  .of("testName", circuitBreakerConfig);

另外,你可以使用CircuitBreakerRegistry的建造方法创建:

Map <String, String> circuitBreakerTags = Map.of("key1", "value1", "key2", "value2");

CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.custom()
    .withCircuitBreakerConfig(CircuitBreakerConfig.ofDefaults())
    .addRegistryEventConsumer(new RegistryEventConsumer() {
        @Override
        public void onEntryAddedEvent(EntryAddedEvent entryAddedEvent) {
            // implementation
        }
        @Override
        public void onEntryRemovedEvent(EntryRemovedEvent entryRemoveEvent) {
            // implementation
        }
        @Override
        public void onEntryReplacedEvent(EntryReplacedEvent entryReplacedEvent) {
            // implementation
        }
    })
    .withTags(circuitBreakerTags)
    .build();

CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("testName");

        如果你想插入自己的Registry实现,可以使用builder方法提供的接口RegistryStore和插件的自定义实现。

CircuitBreakerRegistry registry = CircuitBreakerRegistry.custom()
    .withRegistryStore(new YourRegistryStoreImplementation())
    .withCircuitBreakerConfig(CircuitBreakerConfig.ofDefaults())
    .build();

六、修饰并执行函数式接口

        你可以使用CircuitBreaker断路器修饰Callable、Supplier、Runnable、Consumer、CheckedRunnable、CheckedSupplier、CheckedConsumer或CompletionStage中的任何一个函数式接口。可以调用通过Try.of(…)或Try.run(…)修饰的函数,可以级联更多的函数,像map、flatMap、filter、recover或andThen,关联的函数仅仅是被调用。如果CircuitBreaker断路器的状态是CLOSED或HALF_OPEN,示例如下:

// Given
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("testName");

// When I decorate my function
CheckedFunction0<String> decoratedSupplier = CircuitBreaker
        .decorateCheckedSupplier(circuitBreaker, () -> "This can be any method which returns: 'Hello");

// and chain an other function with map
Try<String> result = Try.of(decoratedSupplier)
                .map(value -> value + " world'");

// Then the Try Monad returns a Success<String>, if all functions ran successfully.
assertThat(result.isSuccess()).isTrue();
assertThat(result.get()).isEqualTo("This can be any method which returns: 'Hello world'");

七、消费发布的RegistryEvents时间

        可以在CircuitBreakerRegistry上注册事件消费者,无论什么时候发生创建、替换或者删除都会触发消费;

CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults();
circuitBreakerRegistry.getEventPublisher()
  .onEntryAdded(entryAddedEvent -> {
    CircuitBreaker addedCircuitBreaker = entryAddedEvent.getAddedEntry();
    LOG.info("CircuitBreaker {} added", addedCircuitBreaker.getName());
  })
  .onEntryRemoved(entryRemovedEvent -> {
    CircuitBreaker removedCircuitBreaker = entryRemovedEvent.getRemovedEntry();
    LOG.info("CircuitBreaker {} removed", removedCircuitBreaker.getName());
  });

八、消费发布的CircuitBreakerEvents事件

CircuitBreakerEvent可以是状态转换、断路器重置、成功调用、记录的错误或忽略的错误。所有的时间都包含一些其它信息,如时间创建时间和调用处理持续时间,如果要消费事件,必须注册事件消费者。

circuitBreaker.getEventPublisher()
    .onSuccess(event -> logger.info(...))
    .onError(event -> logger.info(...))
    .onIgnoredError(event -> logger.info(...))
    .onReset(event -> logger.info(...))
    .onStateTransition(event -> logger.info(...));
// Or if you want to register a consumer listening
// to all events, you can do:
circuitBreaker.getEventPublisher()
    .onEvent(event -> logger.info(...));

可以使用CircularEventConsumer将时间存储在具有固定容量的循环缓冲区中。

CircularEventConsumer<CircuitBreakerEvent> ringBuffer = 
  new CircularEventConsumer<>(10);
circuitBreaker.getEventPublisher().onEvent(ringBuffer);
List<CircuitBreakerEvent> bufferedEvents = ringBuffer.getBufferedEvents()

九、覆盖RegistryStore

你可以通过自定义实现RegistryStore接口来覆盖基于内存实现的RegistryStore,例如:如果你想使用缓存,在一段时间后删除未使用的实例。

CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.custom()
  .withRegistryStore(new CacheCircuitBreakerRegistryStore())
  .build();

十、CircuitBreaker配置案例

// 1. 所有Exception以及其子类都认为是失败.
// 2. 滑动窗口采用基于计时的,并且记录最近10秒的请求.
// 3. 触发断路器判断必须在10秒内至少有5个请求,在失败比例达到30%以上之后,断路器变为:OPEN.  
// 4. 断路器OPEN之后,在2秒后自动转化为HALF_OPEN.
// 5. 断路器在HALF_OPEN之后,允许通过的请求数量为:3个
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
        // 滑动窗口类型(TIME_BASED:时间 / COUNT_BASED:计数器 )
        .slidingWindowType(SlidingWindowType.TIME_BASED)
        // 滑动窗口大小(记录最近10秒的请求)
        .slidingWindowSize(10)
        // 最小请求个数.只有在滑动窗口内,请求个数达到这个个数,才会触发CircuitBreaker对于是否打开断路器的判断
        .minimumNumberOfCalls(5)
        // 当CircuitBreaker处于HALF_OPEN状态的时候,允许通过的请求数量
        .permittedNumberOfCallsInHalfOpenState(3)
        // 自动从OPEN状态变成HALF_OPEN,即使没有请求过来.
        .automaticTransitionFromOpenToHalfOpenEnabled(true)
        // 从OPEN状态变成HALF_OPEN状态需要的等待2秒
        .waitDurationInOpenState(Duration.ofSeconds(2))
        // 失败率达到30%,CircuitBreaker就会变成OPEN状态
        .failureRateThreshold(30)
        // 所有Exception异常会统计为失败.
        .recordExceptions(Exception.class)
        // 
        .build();

(4). pom.xml

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Greenwich.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.1.0.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-netflix</artifactId>
            <version>2.1.1.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
     <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency> 
        <groupId>io.github.resilience4j</groupId>
        <artifactId>resilience4j-spring</artifactId>
        <version>1.7.0</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <scope>test</scope>
    </dependency>
    
    <dependency>
        <groupId>org.assertj</groupId>
        <artifactId>assertj-core</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>


(5). HelloWorldService

HelloWorldService实际为业务代码

package help.lixin.resilience4j;

import io.vavr.control.Either;
import io.vavr.control.Try;

import java.io.IOException;
import java.util.concurrent.Future;

public interface HelloWorldService {
    
    String returnHelloWorld();

    Future<String> returnHelloWorldFuture();

    Either<HelloWorldException, String> returnEither();

    Try<String> returnTry();

    String returnHelloWorldWithException() throws IOException;

    String returnHelloWorldWithName(String name);

    String returnHelloWorldWithNameWithException(String name) throws IOException;

    void sayHelloWorld();

    void sayHelloWorldWithException() throws IOException;

    void sayHelloWorldWithName(String name);

    void sayHelloWorldWithNameWithException(String name) throws IOException;
}


(6). HelloWorldException

HelloWorldException为自定义的业务异常.

package help.lixin.resilience4j;

public class HelloWorldException extends RuntimeException {

    public HelloWorldException() {
        super("BAM!");
    }

    public HelloWorldException(String message) {
        super(message);
    }
}

(7). CircuitBreakerTest
CircuitBreakerTest单元测试

@Test
public void test() {
    // 1. 所有Exception以及其子类都认为是失败.
    // 2. 滑动窗口采用基于统计的,并且记录最近10个的请求.
    // 3. 触发断路器判断至少有5个请求,在失败比例达到20%以上之后,断路器变为:OPEN.
    // 4. 断路器OPEN之后,在2秒后自动转化为HALF_OPEN.
    // 5. 断路器在HALF_OPEN之后,允许通过的请求数量为:3个
    CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
            // 滑动窗口类型(TIME_BASED:时间 / COUNT_BASED:计数器 )
            .slidingWindowType(SlidingWindowType.COUNT_BASED)
            // 滑动窗口大小(统计最近的10个请求)
            .slidingWindowSize(10)
            // 最小请求个数.只有在滑动窗口内,请求个数达到这个个数,才会触发CircuitBreaker对于是否打开断路器的判断
            .minimumNumberOfCalls(5)
            // 当CircuitBreaker处于HALF_OPEN状态的时候,允许通过的请求数量
            .permittedNumberOfCallsInHalfOpenState(3)
            // 自动从OPEN状态变成HALF_OPEN,即使没有请求过来.
            .automaticTransitionFromOpenToHalfOpenEnabled(true)
            // 从OPEN状态变成HALF_OPEN状态需要的等待2秒
            .waitDurationInOpenState(Duration.ofSeconds(2))
            // 失败率达到30%,CircuitBreaker就会变成OPEN状态
            .failureRateThreshold(20)
            // 所有Exception异常会统计为失败.
            .recordExceptions(Exception.class)
            //
            .build();
    
        CircuitBreakerRegistry registry = CircuitBreakerRegistry.of(circuitBreakerConfig);
        CircuitBreaker circuitBreaker = registry.circuitBreaker("test");

        // 业务代码
        HelloService helloService = new HelloService();
        for (int i = 1; i <= 12; i++) {
            int index = i;
            Supplier<String> supplier = circuitBreaker.decorateSupplier(() -> {
                return helloService.sayHello(index);
            });

            if (i == 6) {
                try {
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println("****************************************************************");
                } catch (InterruptedException e1) {
                }
            }

            Try.ofSupplier(supplier) //
                    .onSuccess(s -> {
                        System.out.println(new Date() + "  " + s + "  " + circuitBreaker.getState());
                    }).onFailure(e -> {
                        System.err.println(new Date() + "  " + e.getMessage() + "  " + circuitBreaker.getState());
                    });
        } // end  for
}// end test


// HelloService
class HelloService {
    public String sayHello(int index) {
        if (index < 6 && index % 2 == 0) {
            throw new RuntimeException("fail index->" + index);
        }
        return "success index : " + index;
    }
}

(8). 日志验证

# 请求成功(CircuitBreaker状态为:CLOSE)
Sat May 15 21:49:29 CST 2021  success index : 1  CLOSED
# 请求失败(CircuitBreaker状态为:CLOSE)
Sat May 15 21:49:29 CST 2021  fail index->2  CLOSED
# 请求成功(CircuitBreaker状态为:CLOSE)
Sat May 15 21:49:29 CST 2021  success index : 3  CLOSED
# 请求失败(CircuitBreaker状态为:CLOSE)
Sat May 15 21:49:29 CST 2021  fail index->4  CLOSED

# ************************************************************
# 从第5个请求开始计算失败率CircuitBreaker状态为:OPEN)
Sat May 15 21:49:29 CST 2021  success index : 5  OPEN

# ************************************************************
# 在第6个请求的时候,我让程序休眠了3秒,因为,状态一旦OPEN之后,所有的请求都是会被拒绝的,只有等待2秒后,回归到:HALF_OPEN,才开始接受一部份请求.
# OPEN的时间为:29秒,HALF_OPEN的时间为:32秒
Sat May 15 21:49:32 CST 2021  success index : 6  HALF_OPEN
Sat May 15 21:49:32 CST 2021  success index : 7  HALF_OPEN

# 开始正确接受请求(CircuitBreaker状态为:CLOSE)
Sat May 15 21:49:32 CST 2021  success index : 8  CLOSED
Sat May 15 21:49:32 CST 2021  success index : 9  CLOSED
Sat May 15 21:49:32 CST 2021  success index : 10  CLOSED
Sat May 15 21:49:32 CST 2021  success index : 11  CLOSED
Sat May 15 21:49:32 CST 2021  success index : 12  CLOSED

十、resilience4j链接

     https://resilience4j.readme.io/docs/circuitbreaker

另外一个GitHub地址参考: https://github.com/mingyang66/EmilyGateway


参考:

CircuitBreaker

Resilience4j Circuit Breaker使用案例(二) - 李新的博客

Resilience4j 实用指南_语言 & 开发_金科优源汇_InfoQ精选文章

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值