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、最佳实践
为了最大限度地发挥这些模式的作用:
- 监控指标:持续监控故障率、重试尝试和断路器状态,以便对配置进行微调。
- 组合模式:对瞬时错误使用重试,对系统故障使用断路器。
- 设置切合实际的阈值:过于激进的阈值会阻碍恢复或延迟故障检测。
- 利用库:使用强大的库,如 Resilience4j 或 Spring Cloud Circuit Breaker(底层实现了 Resilience4j 和 Spring Retry),以简化实现。
7、整合 Spring Boot
Spring Boot 通过其生态系统为熔断器和重试模式提供全面支持。这种集成主要是通过 Spring Cloud Circuit Breaker 项目和 Spring Retry 模块实现的。
Spring Cloud Breaker 项目提供了一个抽象层,使我们能够实现断路器,而无需绑定到特定的实现。这意味着我们可以根据需要在不同的断路器实现(如 Resilience4j、Hysterix、Sentinel 或 Spring Retry)之间切换,而无需更改应用程序代码。该项目使用 Spring Boot 的自动配置机制,当它在类路径中检测到合适的 Starter 时,会自动配置必要的断路器 Bean。
在重试功能方面,Spring Boot 与 Spring Retry 集成,提供基于注解和编程式的方法来实现重试逻辑。该框架通过 properties 文件和 Java 配置提供灵活的配置选项,允许我们自定义重试尝试、回退策略和恢复策略。
来看看 Spring Boot 与这些模式集成的几个特点,它们使 Spring Boot 变得尤为强大:
- 支持自动配置:Spring Boot 可根据类路径中的依赖自动配置断路器和重试 bean,从而减少模板配置代码。
- 可插拔架构:抽象层允许我们在不同的断路器实现之间切换,而无需修改业务逻辑。
- 配置灵活:这两种模式都可以通过 application properties 或 Java 配置进行配置,支持针对不同服务的全局和特定配置。
- 与 Spring 生态系统集成:这些模式可与其他 Spring 组件(如
RestTemplate
、WebClient
和各种 Spring Cloud 组件)无缝协作。 - 监控和指标:Spring Boot 的 Actuator 集成为断路器和重试尝试提供了内置监控功能,帮助我们跟踪弹性机制的健康状况和行为。
这种集成方法符合 Spring Boot 惯例重于配置的理念,同时保持了在需要时自定义行为的灵活性。该框架对这些模式的支持使构建有弹性的微服务变得更容易,这些微服务可以优雅地处理故障并保持系统稳定。
8、总结
重试和断路器都是分布式系统中必不可少的恢复模式。重试侧重于即时恢复,而断路器则提供强大的保护,防止级联故障。通过了解它们的区别和使用案例,我们可以设计出既可靠又容错的系统。
借助 Resilience4j 和 Spring Cloud Circuit Breaker 等库,Spring Boot 为轻松实现这些模式提供了一个强大的平台。通过采用这些弹性策略,我们可以构建能够从容应对故障的应用程序,即使在不利条件下也能确保无缝的用户体验。
Ref:https://www.baeldung.com/spring-boot-circuit-breaker-vs-retry