好的,我们开始。
CircuitBreaker:防止服务雪崩的利器,Resilience4j 实战讲解
大家好!今天我们来深入探讨一下如何使用 CircuitBreaker 模式,特别是通过 Resilience4j 库,来有效地防止服务雪崩。服务雪崩是分布式系统中一个非常常见且危险的问题,它会导致整个系统的崩溃。CircuitBreaker 正是用来解决这个问题的关键工具。
1. 什么是服务雪崩?
想象一下,你的电商网站依赖于多个微服务,比如订单服务、支付服务、库存服务等。如果某个服务(比如库存服务)因为某种原因变得缓慢或不可用,那么调用该服务的其他服务(例如订单服务)将会被阻塞,等待响应。
由于订单服务无法从库存服务获取信息,它会继续重试,消耗大量的线程和资源。如果大量请求同时涌入,订单服务自身的资源也会耗尽,最终导致订单服务也变得缓慢或不可用。
这种延迟和失败会像雪崩一样,迅速蔓延到整个系统,导致整个系统瘫痪。这就是服务雪崩。
2. CircuitBreaker 模式
CircuitBreaker 模式的核心思想是:当一个服务调用失败达到一定阈值时,熔断器会打开,阻止后续的调用请求,避免继续消耗资源,并给下游服务提供恢复的时间。
CircuitBreaker 模式包含三个状态:
- Closed(关闭): 默认状态。服务调用正常进行。如果失败率超过预设的阈值,熔断器会切换到 Open 状态。
- Open(打开): 熔断器打开,所有服务调用都会被立即拒绝,不会实际调用下游服务。一段时间后,熔断器会进入 Half-Open 状态。
- Half-Open(半开): 熔断器允许部分请求通过,尝试调用下游服务。如果调用成功,熔断器会切换回 Closed 状态。如果调用仍然失败,熔断器会切换回 Open 状态。
3. Resilience4j 简介
Resilience4j 是一个轻量级的容错库,它提供了 CircuitBreaker、RateLimiter、Retry、Bulkhead 等多种容错模式的实现。它使用简单,配置灵活,与 Spring Cloud 等框架集成方便。
4. Resilience4j CircuitBreaker 实战
接下来,我们通过一个简单的例子来演示如何使用 Resilience4j CircuitBreaker。
4.1 添加依赖
首先,在你的 Maven 或 Gradle 项目中添加 Resilience4j 的依赖:
<!-- Maven -->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-circuitbreaker</artifactId>
<version>2.2.0</version>
</dependency>
<!-- Spring Boot Starter Actuator for monitoring -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Spring Boot AOP Starter for aspect-oriented programming -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
// Gradle
implementation 'io.github.resilience4j:resilience4j-circuitbreaker:2.2.0'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-aop'
4.2 配置 CircuitBreaker
我们可以通过 Java 代码或配置文件来配置 CircuitBreaker。这里我们使用配置文件的方式。
在 application.properties 或 application.yml 中添加以下配置:
resilience4j:
circuitbreaker:
instances:
myCircuitBreaker:
registerHealthIndicator: true # 注册到 Spring Boot Actuator
failureRateThreshold: 50 # 失败率阈值,超过 50% 则打开熔断器
minimumNumberOfCalls: 10 # 触发熔断器所需的最小调用次数
automaticTransitionFromOpenToHalfOpenEnabled: true # 允许自动从 Open 状态转换为 Half-Open 状态
waitDurationInOpenState: 5s # Open 状态持续时间,5 秒后进入 Half-Open 状态
permittedNumberOfCallsInHalfOpenState: 3 # Half-Open 状态允许的调用次数
slidingWindowSize: 10 # 滑动窗口大小,用于计算失败率
slidingWindowType: COUNT_BASED # 滑动窗口类型,基于调用次数
上述配置的含义:
failureRateThreshold: 失败率阈值,当失败率超过 50% 时,熔断器打开。minimumNumberOfCalls: 触发熔断器所需的最小调用次数,只有当调用次数达到这个值,才会计算失败率。automaticTransitionFromOpenToHalfOpenEnabled: 是否允许自动从 Open 状态转换为 Half-Open 状态。waitDurationInOpenState: Open 状态持续时间,5 秒后进入 Half-Open 状态。permittedNumberOfCallsInHalfOpenState: Half-Open 状态允许的调用次数。slidingWindowSize: 滑动窗口大小,用于计算失败率。slidingWindowType: 滑动窗口类型,这里选择基于调用次数的窗口。
4.3 使用 CircuitBreaker
现在,我们创建一个服务,并使用 CircuitBreaker 来保护它。
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.springframework.stereotype.Service;
import java.util.Random;
@Service
public class MyService {
private final Random random = new Random();
@CircuitBreaker(name = "myCircuitBreaker", fallbackMethod = "fallback")
public String callExternalService() {
// 模拟调用外部服务
if (random.nextInt(10) < 6) { // 60% 的概率失败
throw new RuntimeException("External service failed!");
}
return "External service response";
}
public String fallback(Throwable t) {
// 降级方法,当熔断器打开时,会调用此方法
return "Fallback response: " + t.getMessage();
}
}
代码解释:
@CircuitBreaker(name = "myCircuitBreaker", fallbackMethod = "fallback"): 使用@CircuitBreaker注解将callExternalService方法包裹起来。name属性指定了 CircuitBreaker 的名称,fallbackMethod属性指定了降级方法。callExternalService(): 模拟调用外部服务,60% 的概率会抛出异常。fallback(Throwable t): 降级方法,当熔断器打开时,会调用此方法。
4.4 测试 CircuitBreaker
创建一个 Controller 来测试 CircuitBreaker。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@Autowired
private MyService myService;
@GetMapping("/call")
public String callService() {
return myService.callExternalService();
}
}
启动 Spring Boot 应用,并多次访问 /call 接口。你会发现,当失败率达到 50% 时,熔断器会打开,后续的请求会直接返回 fallback 方法的结果。
4.5 使用 Spring AOP 的方式
除了使用注解,还可以通过 Spring AOP 的方式来应用 CircuitBreaker。首先,你需要配置一个 Aspect。
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.core.functions.CheckedSupplier;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class CircuitBreakerAspect {
private final CircuitBreakerRegistry circuitBreakerRegistry;
@Autowired
public CircuitBreakerAspect(CircuitBreakerRegistry circuitBreakerRegistry) {
this.circuitBreakerRegistry = circuitBreakerRegistry;
}
@Around("@annotation(myCircuitBreakerAnnotation)")
public Object circuitBreakerAroundAdvice(ProceedingJoinPoint joinPoint, MyCircuitBreaker myCircuitBreakerAnnotation) throws Throwable {
String circuitBreakerName = myCircuitBreakerAnnotation.name();
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(circuitBreakerName);
CheckedSupplier<Object> decoratedSupplier = CircuitBreaker.decorateCheckedSupplier(circuitBreaker, joinPoint::proceed);
try {
return decoratedSupplier.get();
} catch (Throwable throwable) {
// Handle exceptions and fallback here
return handleFallback(throwable, myCircuitBreakerAnnotation.fallbackMethod(), joinPoint);
}
}
private Object handleFallback(Throwable throwable, String fallbackMethodName, ProceedingJoinPoint joinPoint) throws Throwable {
try {
// Reflection to invoke the fallback method
return joinPoint.getTarget().getClass().getMethod(fallbackMethodName, Throwable.class).invoke(joinPoint.getTarget(), throwable);
} catch (NoSuchMethodException e) {
// Fallback method not found
throw new RuntimeException("Fallback method not found: " + fallbackMethodName, e);
} catch (Exception e) {
// Error invoking fallback method
throw new RuntimeException("Error invoking fallback method: " + fallbackMethodName, e);
}
}
}
定义一个注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyCircuitBreaker {
String name();
String fallbackMethod();
}
然后,在 MyService 中使用这个注解:
import org.springframework.stereotype.Service;
import java.util.Random;
@Service
public class MyService {
private final Random random = new Random();
@MyCircuitBreaker(name = "myCircuitBreaker", fallbackMethod = "fallback")
public String callExternalService() {
// 模拟调用外部服务
if (random.nextInt(10) < 6) { // 60% 的概率失败
throw new RuntimeException("External service failed!");
}
return "External service response";
}
public String fallback(Throwable t) {
// 降级方法,当熔断器打开时,会调用此方法
return "Fallback response: " + t.getMessage();
}
}
4.6 监控 CircuitBreaker 状态
Resilience4j 与 Spring Boot Actuator 集成,可以方便地监控 CircuitBreaker 的状态。
在 application.properties 或 application.yml 中启用 Actuator:
management:
endpoints:
web:
exposure:
include: health,metrics
访问 http://localhost:8080/actuator/health 可以查看 CircuitBreaker 的健康状态。
访问 http://localhost:8080/actuator/metrics/resilience4j.circuitbreaker.state 可以查看 CircuitBreaker 的状态指标。
5. CircuitBreaker 配置参数详解
以下是一些常用的 CircuitBreaker 配置参数:
| 参数名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
registerHealthIndicator |
Boolean | true |
是否注册到 Spring Boot Actuator 的健康指示器。 |
failureRateThreshold |
Integer | 50 |
失败率阈值,当失败率超过此值时,熔断器打开。 |
minimumNumberOfCalls |
Integer | 100 |
触发熔断器所需的最小调用次数。只有当调用次数达到此值时,才会计算失败率。 |
automaticTransitionFromOpenToHalfOpenEnabled |
Boolean | false |
是否允许自动从 Open 状态转换为 Half-Open 状态。如果设置为 false,则需要手动触发状态转换。 |
waitDurationInOpenState |
Duration | 60s |
Open 状态的持续时间。在此时间之后,熔断器将转换为 Half-Open 状态。 |
permittedNumberOfCallsInHalfOpenState |
Integer | 10 |
Half-Open 状态允许的调用次数。如果在 Half-Open 状态下,所有调用都成功,则熔断器将转换为 Closed 状态。如果在 Half-Open 状态下,有任何调用失败,则熔断器将转换为 Open 状态。 |
slidingWindowSize |
Integer | 100 |
滑动窗口的大小,用于计算失败率。 |
slidingWindowType |
Enum | COUNT_BASED |
滑动窗口的类型。可以是 COUNT_BASED(基于调用次数)或 TIME_BASED(基于时间)。 |
recordExceptions |
List<Class<? extends Throwable>> | [] |
需要记录的异常类型列表。只有当抛出这些异常时,才会增加失败计数。如果为空,则记录所有异常。 |
ignoreExceptions |
List<Class<? extends Throwable>> | [] |
需要忽略的异常类型列表。当抛出这些异常时,不会增加失败计数。 |
6. 最佳实践
- 合理设置阈值:
failureRateThreshold和minimumNumberOfCalls需要根据实际情况进行调整。 - 选择合适的滑动窗口类型:
COUNT_BASED和TIME_BASED滑动窗口各有优缺点,根据你的应用场景选择合适的类型。 - 提供降级策略: 当熔断器打开时,需要提供合适的降级策略,例如返回默认值、从缓存中读取数据等。
- 监控 CircuitBreaker 状态: 通过 Spring Boot Actuator 监控 CircuitBreaker 的状态,及时发现和解决问题。
- 考虑使用 Bulkhead 和 RateLimiter: CircuitBreaker 只能防止服务雪崩,但不能解决资源耗尽的问题。可以结合 Bulkhead 和 RateLimiter 来限制并发和请求速率,进一步提高系统的稳定性。
7. 高级用法
- 自定义 CircuitBreakerEventListener: 可以自定义
CircuitBreakerEventListener来监听 CircuitBreaker 的状态变化,例如记录日志、发送告警等。 - 动态配置 CircuitBreaker: 可以通过配置中心(例如 Spring Cloud Config)来动态修改 CircuitBreaker 的配置参数。
- 与其他容错模式结合使用: 可以将 CircuitBreaker 与 Retry、Bulkhead 等容错模式结合使用,构建更强大的容错机制。
8. 关于Resilience4j版本选择的说明
Resilience4j的版本迭代较快,不同的版本之间可能存在一些差异。建议选择较新的稳定版本。在编写本文时,最新的稳定版本是 2.2.0。在实际使用时,请查阅官方文档,选择适合你的项目的版本。
总结:CircuitBreaker 是关键,配置和监控同等重要
CircuitBreaker 模式是防止服务雪崩的有效手段。通过 Resilience4j 库,我们可以轻松地实现 CircuitBreaker,并与其他容错模式结合使用,构建更健壮的分布式系统。 合理的配置和及时的监控是保证CircuitBreaker有效性的关键。