JAVA 如何用 CircuitBreaker 防止服务雪崩?Resilience4j 实战讲解

好的,我们开始。

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.propertiesapplication.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.propertiesapplication.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. 最佳实践

  • 合理设置阈值: failureRateThresholdminimumNumberOfCalls 需要根据实际情况进行调整。
  • 选择合适的滑动窗口类型: COUNT_BASEDTIME_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有效性的关键。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注