Spring Boot 中 Circuit Breaker 与 Retry 的区别

1、概览

在分布式系统和微服务架构中,优雅地处理故障对于保持系统可靠性和性能至关重要。断路器(Circuit Breaker,也称为熔断器)和重试(Retry)是有助于实现这一目标的两种基本弹性模式。虽然这两种模式都旨在提高系统的稳定性和可靠性,但它们的目的截然不同,适用于不同的场景。

本文将带你深入了解这些模式,包括它们的机制、用例以及在 Spring Boot 中使用 Resilience4j 实现的细节。

2、什么是重试?

重试模式是一种简单而强大的机制,用于处理分布式系统中的瞬时故障。当操作失败时,重试模式会尝试多次执行相同的操作,希望临时问题能自行解决。

2.1、重试的主要特点

重试机制围绕特定属性展开,这些属性使重试机制能够有效处理瞬时问题,确保临时故障不会升级为重大问题:

  • 重复尝试:核心理念是对失败的操作重新执行指定次数。
  • Backoff(回退)策略:这是一种高级的重试机制,其中包括退避策略,如指数退避,有助于避免对系统造成过大压力。
  • 适用于临时故障:最适合处理间歇性网络问题、临时服务不可用或瞬间资源紧张的情况。

2.2、重试示例

来看一个使用 Resilience4j 实现重试机制的简单示例:

@Test
public void whenRetryWithExponentialBackoffIsUsed_thenItRetriesAndSucceeds() {
    IntervalFunction intervalFn = IntervalFunction.ofExponentialBackoff(1000, 2);
    RetryConfig retryConfig = RetryConfig.custom()
        .maxAttempts(5)
        .intervalFunction(intervalFn)
        .build();

    Retry retry = Retry.of("paymentRetry", retryConfig);

    when(paymentService.process(1)).thenThrow(new RuntimeException("First Failure"))
          .thenThrow(new RuntimeException("Second Failure"))
          .thenReturn("Success");

    Callable<String> decoratedCallable = Retry.decorateCallable(
      retry, () -> paymentService.processPayment(1)
    );

    try {
        String result = decoratedCallable.call();
        assertEquals("Success", result);
    } catch (Exception ignored) {
    }

    verify(paymentService, times(3)).processPayment(1);
}

如上例:

  • 重试机制最多可尝试操作五次
  • 它采用指数后退策略,在两次尝试之间引入延迟,降低了系统超负荷的风险
  • 重试两次后操作成功

3、什么是断路器模式?

断路器模式(Circuit Breaker pattern)是一种更先进的故障处理方法。它能防止应用程序重复尝试执行可能会失败的操作,从而防止级联故障并提供系统稳定性。

3.1、断路器的主要特点

断路器模式的重点是防止故障服务负载过大,并减轻连锁故障(服务雪崩)。它的主要属性如下:

  • 状态管理:断路器有三种主要状态
    • Closed(关闭):正常运行,允许请求继续进行
    • Open(打开):阻止所有请求,防止出现进一步故障
    • Half-Open(半开):允许数量有限的测试请求,以检查系统是否已经恢复
  • 故障阈值:监控滑动窗口内失败请求的百分比,并在失败率超过配置阈值时 “跳闸(熔断)” 电路
  • 防止级联故障:停止对故障服务的重复调用,防止整个系统性能下降

3.2、断路器示例

下面是一个简单的断路器实现示例,展示了状态转换:

@Test
public void whenCircuitBreakerTransitionsThroughStates_thenBehaviorIsVerified() {
    CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
        .failureRateThreshold(50)
        .slidingWindowSize(5)
        .permittedNumberOfCallsInHalfOpenState(3)
        .build();

    CircuitBreaker circuitBreaker = CircuitBreaker.of("paymentCircuitBreaker", circuitBreakerConfig);

    AtomicInteger callCount = new AtomicInteger(0);

    when(paymentService.processPayment(anyInt())).thenAnswer(invocationOnMock -> {
        callCount.incrementAndGet();
        throw new RuntimeException("Service Failure");
    });

    Callable<String> decoratedCallable = CircuitBreaker.decorateCallable(
      circuitBreaker, () -> paymentService.processPayment(1)
    );

    for (int i = 0; i < 10; i++) {
        try {
            decoratedCallable.call();
        } catch (Exception ignored) {
        }
    }

    assertEquals(5, callCount.get());
    assertEquals(CircuitBreaker.State.OPEN, circuitBreaker.getState());

    callCount.set(0);
    circuitBreaker.transitionToHalfOpenState();

    assertEquals(CircuitBreaker.State.HALF_OPEN, circuitBreaker.getState());
    reset(paymentService);
    when(paymentService.processPayment(anyInt())).thenAnswer(invocationOnMock -> {
        callCount.incrementAndGet();
        return "Success";
    });

    for (int i = 0; i < 3; i++) {
        try {
            decoratedCallable.call();
        } catch (Exception ignored) {
        }
    }

    assertEquals(3, callCount.get());
    assertEquals(CircuitBreaker.State.CLOSED, circuitBreaker.getState());
}

如上例:

  • 断路器何时 “跳闸” 由 50% 的故障率阈值和一个包含五次调用的滑动窗口确定。
  • 五次尝试失败后,断路器会打开,立即拒绝进一步调用。
  • 等待一秒后,断路器转换为半开。
  • 在 “半开” 状态下,三次调用成功后,断路器将转为 “闭合” 状态,恢复正常运行。

4、主要区别:重试与断路器

比较面 重试模式 断路器模式
主要目标 多次尝试进行操作 防止重复调用出现故障的服务
故障处理 假定出现瞬时故障 假设可能出现系统故障
状态管理 无状态,反复尝试 保持状态(关闭/打开/半开)
最佳用途 间歇性、可恢复的错误 持续性或系统性故障

5、何时使用每种模式

决定何时使用重试或断路器取决于系统遇到的故障类型。这些模式相辅相成,了解它们的应用有助于我们构建能有效处理错误的弹性系统。

  • 在以下情况下使用重试:
    • 处理瞬时网络问题
    • 预计会出现暂时无法提供服务的情况
    • 只需重试几次即可快速恢复
  • 在下列情况下使用断路器:
    • 防止长期服务故障
    • 防止微服务出现连锁故障
    • 实现自我修复系统架构

在实际应用中,这些模式经常一起使用。例如,重试机制可以在断路器的范围内工作,确保只有在电路关闭或半开时才尝试重试。

6、最佳实践

为了最大限度地发挥这些模式的作用:

  • 监控指标:持续监控故障率、重试尝试和断路器状态,以便对配置进行微调。
  • 组合模式:对瞬时错误使用重试,对系统故障使用断路器。
  • 设置切合实际的阈值:过于激进的阈值会阻碍恢复或延迟故障检测。
  • 利用库:使用强大的库,如 Resilience4jSpring Cloud Circuit Breaker(底层实现了 Resilience4jSpring Retry),以简化实现。

7、整合 Spring Boot

Spring Boot 通过其生态系统为熔断器和重试模式提供全面支持。这种集成主要是通过 Spring Cloud Circuit Breaker 项目和 Spring Retry 模块实现的。

Spring Cloud Breaker 项目提供了一个抽象层,使我们能够实现断路器,而无需绑定到特定的实现。这意味着我们可以根据需要在不同的断路器实现(如 Resilience4jHysterixSentinelSpring Retry)之间切换,而无需更改应用程序代码。该项目使用 Spring Boot 的自动配置机制,当它在类路径中检测到合适的 Starter 时,会自动配置必要的断路器 Bean。

在重试功能方面,Spring Boot 与 Spring Retry 集成,提供基于注解和编程式的方法来实现重试逻辑。该框架通过 properties 文件和 Java 配置提供灵活的配置选项,允许我们自定义重试尝试、回退策略和恢复策略。

来看看 Spring Boot 与这些模式集成的几个特点,它们使 Spring Boot 变得尤为强大:

  • 支持自动配置:Spring Boot 可根据类路径中的依赖自动配置断路器和重试 bean,从而减少模板配置代码。
  • 可插拔架构:抽象层允许我们在不同的断路器实现之间切换,而无需修改业务逻辑。
  • 配置灵活:这两种模式都可以通过 application properties 或 Java 配置进行配置,支持针对不同服务的全局和特定配置。
  • 与 Spring 生态系统集成:这些模式可与其他 Spring 组件(如 RestTemplateWebClient 和各种 Spring Cloud 组件)无缝协作。
  • 监控和指标:Spring Boot 的 Actuator 集成为断路器和重试尝试提供了内置监控功能,帮助我们跟踪弹性机制的健康状况和行为。

这种集成方法符合 Spring Boot 惯例重于配置的理念,同时保持了在需要时自定义行为的灵活性。该框架对这些模式的支持使构建有弹性的微服务变得更容易,这些微服务可以优雅地处理故障并保持系统稳定。

8、总结

重试和断路器都是分布式系统中必不可少的恢复模式。重试侧重于即时恢复,而断路器则提供强大的保护,防止级联故障。通过了解它们的区别和使用案例,我们可以设计出既可靠又容错的系统。

借助 Resilience4jSpring Cloud Circuit Breaker 等库,Spring Boot 为轻松实现这些模式提供了一个强大的平台。通过采用这些弹性策略,我们可以构建能够从容应对故障的应用程序,即使在不利条件下也能确保无缝的用户体验。


Ref:https://www.baeldung.com/spring-boot-circuit-breaker-vs-retry