Java:Resilience4j CircuitBreaker入门指南

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

目录

介绍

基于计数的滑动窗口

基于时间的滑动窗口

失败率和慢调用率阈值

创建CircuitBreakerRegistry

创建和配置CircuitBreaker

装配和执行函数式接口

消费发出的RegistryEvent

消费发出的CircuitBreakerEvent


介绍

CircuitBreaker是由一个有限状态机实现的,其中包含三个一般性状态:CLOSED, OPEN, HALF_OPEN(关闭、打开、半开)和两个特定状态:DISABLED, FORCED_OPEN(禁用、强开)。


CircuitBreaker使用滑动窗口(sliding window)存储和统计调用的结果。你能在基于计数的滑动窗口(count-based sliding window)和基于时间的滑动窗口(time-based sliding window)中作选择。基于计数的滑动窗口统计最近N次调用的结果。基于时间的滑动窗口统计最近N秒的调用结果。

基于计数的滑动窗口

基于计数的滑动窗口是由包含N个测量单元的循环数组实现的。若计数窗口的大小为10,循环数组总会有10个测量单元。滑动窗口会增量更新总统计值。当新的调用结果被记录后,总统计值就会被更新。当最老的测量单元被排除,这个测量单元就会从总统计值中减掉,占用的桶就会被重置。(排除后减掉(Subtract-on-Evict))

获取快照的时间恒定为 O(1),因为快照是已统计好的,并独立于窗口大小。这种实现的空间需求(内存消耗)是O(n)。

基于时间的滑动窗口

基于时间的滑动窗口是由N个部分统计单元(桶)的循环数组实现的。
若滑动窗口大小为10秒,循环数组含有10个部分统计单元(桶)。每个桶统计一个指定秒内的所有调用的结果(部分统计)。循环数组的头部桶存储当前秒内所有调用的结果。其它部分统计单元存储以前秒内的调用结果。
滑动窗口不会单独存储所有调用结果,但会增量更新部分统计单元(桶)中的值和总统计值。
当新的调用结果被记录后,总统计值会增量更新。当最老的桶被排除,这个桶中的部分统计值会从总统计值中减掉,这个桶也会被重置。(排除后减掉(Subtract-on-Evict))

获取快照的时间恒定为 O(1),因为快照是已统计好的,并独立于窗口大小。因为调用结果不会各自单独存储,所以这种实现的空间需求(内存消耗)接近O(n)。是需要创建N个部分统计单元和一个总统计单元。
为了给失败调用次数、慢调用次数和总调用次数计数,部分统计单元是由3个integer组成。并且一个long字段存储总调用时长。

失败率和慢调用率阈值

当失败率等于或大于阈值时,CircuitBreaker的状态由CLOSED转为OPEN。比如,当大于50%被记录的调用失败了。

所有的异常默认都会记为失败。你可以定义一个可以当作失败的异常列表。除此以外的其它异常,除非被忽略,不然都会被当作成功。异常也可以被忽略,这样它既不被当作失败也不被当作成功。

当慢调用的比例等于或大于阈值时,CircuitBreaker的状态由CLOSED转为OPEN。比如,当大于50%被记录的调用花的时间大于5秒。这将有助于在外部系统变得无响应阄,降低它的负载。

只有当记录到的调用次数达到一定的最小数量时,失败率和慢调用率才能被计算出来。比如,若要求调用的最小次数是10,那最少必须记录到10次调用,失败率才能被计算出来。若只评估了9次调用,哪怕这9次调用都失败,CircuitBreaker也不会打开。

当CircuitBreaker状态为OPEN时,调用将被拒绝,并将异常CallNotPermittedException返回给调用者。当经过一定的等待时间后,CircuitBreaker状态会从OPEN变为HALF_OPEN,并允许一定数量的调用去执行,以查看后端是仍然不可用还是已再次可用。除非所有被允许的调用都完成,不然其后的调用都会被拒绝并返回异常CallNotPermittedException。

若失败率和慢调用率仍大于等于配置的阈值,状态会改回OPEN。若失败率和慢调用率低于阈值,状态会改回CLOSED。

CircuitBreaker还支持两种特殊的状态:DISABLED(总是允许访问), FORCED_OPEN(总是拒绝访问)。处于这两种状态时,没有CircuitBreaker事件(除了状态转换)会产生,也不会记录任何度量值。从这两种状态退出的唯一办法就是触发状态转换或重置CircuitBreaker。

CircuitBreaker是线程安全的:

  • CircuitBreaker的状态存放在AtomicReference中。
  • CircuitBreaker使用原子操作更新状态,不会有负作用。
  • 记录调用和从时间窗口中读取快照都是同步处理的。

这意味着保证了原子性,在同一时刻只有一个线程可以更新状态或滑动窗口。

但CircuitBreaker不会同步函数调用。也就是说函数调用本身不是临界区的一个部分。否则CircuitBreaker会带来巨大的性能下降,造成性能瓶颈。慢函数调用会对整个系统的性能/呑吐量造成巨大的负面影响。

若20个并发线程请求执行一个函数,并且CircuitBreaker状态为关闭,所有线程都会被允许执行这个函数。即使滑动窗口的大小为15,并不意味着只能有15个调用并发执行。如果你想限制并发线程数,请使用Bulkhead。你可将Bulkhead与CircuitBreaker一起使用。

1个线程和例子

3个线程的例子

创建CircuitBreakerRegistry

Resilience4j会在内存中带一个CircuitBreakerRegistry,这个CircuitBreakerRegistry基于ConcurrentHashMap,提供了线程安全性和原子性保证。你能使用CircuitBreakerRegistry管理(创建和获取)CircuitBreaker实例。可以使用一个全局默认CircuitBreakerConfig为所有CircuitBreaker实例创建一个CircuitBreakerRegistry。

CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults();

创建和配置CircuitBreaker

你可以创建自己定制的全局CircuitBreakerConfig。为了创建一个全局的CircuitBreakerConfig,可以使用CircuitBreakerConfig构建器。可以使用构建器配置如下属性:

配置属性默认值描述
failureRateThreshold50以百分率形式配置失败率阈值。失败率大于等于阈值时,CircuitBreaker转变为打开状态,并使调用短路。
slowCallRateThreshold100以百分率形式配置慢调用率阈值。当调用执行的时长大于slowCallDurationThreshold时,CircuitBreaker会认为调用为慢调用。当慢调用占比大于等于此阈值时,CircuitBreaker转变为打开状态,并使调用短路。
slowCallDurationThreshold60000 [ms]配置调用执行的时长阈值。当超过这个阈值时,调用会被认为是慢调用,并增加慢调用率。
permittedNumberOfCallsInHalfOpenState10当CircuitBreaker是半开状态时,配置被允许的调用次数。
slidingWindowType

COUNT_BASED

/TIME_BASED

配置滑动窗口类型。当CircuitBreaker关闭时,这种类型的滑动窗口会记录调用结果。滑动窗口要么是基于计数的,要么是基于时间的。若滑动窗口为COUNT_BASED,则最近slidingWindowSize次的调用会被记录和统计。若滑动窗口为TIME_BASED,则最近slidingWindowSize秒中的调用会被记录和统计。
slidingWindowSize100配置滑动窗口的大小。当CircuitBreaker关闭后用于记录调用结果。
minimumNumberOfCalls10配置最小调用次数。在CircuitBreaker计算错误率前,要求(在每滑动窗口周期)用到这个值。例如,若minimumNumberOfCalls是10,为计算失败率,则最小要记录10个调用。若只记录了9个调用,即使9个都失败,CircuitBreaker也不会打开。
waitDurationInOpenState60000 [ms]CircuitBreaker状态从打开转化为半开时,需要等待的时长。
automaticTransitionFromOpenToHalfOpenEnabledfalse如果为true,则CircuitBreaker会自动从打开状态转化为半开状态。不需要另外的调用来触发这种转换。
recordExceptionsempty这个异常列表用来存放被当作失败的异常,这些异常发生时会增加失败率。任何异常,只要不是在ignoreExceptions中被明确忽略的,如果匹配或继承自异常列表中的异常,都会当作失败。若设置了异常列表,不在异常列表中的异常,只要不在ignoreExceptions中存在,都会当作成功。
ignoreExceptionsempty这个异常列表用来存放可忽略的异常,这些异常即不当作成功也不当作失败。任何异常,只要匹配或继承自此异常列表中的异常,都不会当作成功或失败,即使recordExceptions中存在这个异常。
recordException

throwable -> true

所有异常默认都当作失败。

这是一个定制的Predicate,会评估是否把异常当作失败。除非异常在ignoreExceptions中存在,不然,只有Predicate必须返回true时,才把异常当作失败;只有Predicate必须返回false时,才把异常当作成功。
ignoreException

throwable -> false

所有异常默认都不会被忽略。

这是一个定制的Predicate,会评估异常是否应该被忽略,而不是把它当作失败或成功。如果应忽略此异常,则Predicate必须返回true。如果把异常当作失败,则Predicate必须返回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);

可以添加被多个CircuitBreaker实例共享的配置。

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);

装配和执行函数式接口

可以在CircuitBreaker上装配做任意Callable, Supplier, Runnable, Consumer, CheckedRunnable, CheckedSupplier, CheckedConsumer, CompletionStage

可以使用来自Vavr的Try.of(…​)、Try.run(…​)调用装饰函数。这样就可以使用map, flatMap, filter, recover, andThen将更多的函数串起来。这些串起来的函数只有在CircuitBreaker的状态为CLOSED或HALF_OPEN时,才能被调用。

在下面的例子中,如果函数调用成功,Try.of(…​)返回Success<String>。如果函数抛出异常,Failure<Throwable>被返回,并且不会调用map。

// 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'");

消费发出的RegistryEvent

在CircuitBreakerRegistry上注册事件消费者,这样在CircuitBreaker被创建、替代、删除时可以执行一些操作。

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());
  });

消费发出的CircuitBreakerEvent

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()

可以使用RxJava或RxJava2选配器将EventPublisher转化为Reactive Stream。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值