原创

Spring Cloud Circuit Breaker快速入门Demo

1.什么是Spring Cloud Circuit Breaker?

Spring Cloud Circuit breaker提供了一个跨越不同断路器实现的抽象。它提供了一个一致的API,可以在你的应用程序中使用,允许你的开发者选择最适合你的应用程序需求的断路器实现。
它还支持的实现有如下几种
  • Resilience4j
  • Hystrix
  • Sentinel
  • Spring Retry

Resilience4j概述

Resilience4J 是一个针对 Java 8 应用程序的轻量级容错和弹性库。它设计用于在分布式系统中的服务之间提供弹性和容错性。Resilience4J 的名字来源于它提供的核心功能,即让系统(服务)能够“弹性”(resilient)地应对各种失败情况,包括网络问题、第三方服务故障等。 Resilience4J 提供了以下功能:
  1. 断路器(Circuit Breaker):当检测到服务异常或超时,断路器会打开,阻止进一步的请求发送到该服务。一段时间后(通常是秒级),断路器会进入半开状态,允许一个测试请求通过以检查服务是否恢复。如果请求成功,断路器关闭;如果失败,断路器会再次打开。
  2. 限流(Rate Limiter):限制进入系统的请求速率,防止系统过载。这可以通过令牌桶算法或滑动窗口算法实现。
  3. 隔离(Isolation):通过信号量或线程池隔离不同的服务调用,防止一个服务的失败影响到其他服务。
  4. 超时(Timeouts):为服务调用设置超时时间,超过时间后会触发超时异常。
  5. 重试(Retry):在遇到特定异常时自动重试服务调用,可以配置重试次数和间隔。
  6. 缓存(Caching):提供缓存机制,以避免重复执行计算密集型或远程调用。

Resilience4j 的 CircuitBreaker 实现原理如下

  1. 断路器的状态:CircuitBreaker 具有三种正常状态:CLOSED(关闭)、OPEN(打开)和 HALFOPEN(半开),以及两个特殊状态:DISABLED(禁用)和 FORCEDOPEN(强制打开)。这些状态通过有限状态机进行管理。
  2. 打开和关闭逻辑:当被保护的服务或资源发生故障或长时间不可用时,断路器会迅速切换到 OPEN 状态,阻止更多的请求发送到该服务或资源。在 OPEN 状态下,系统会定期发送测试请求,以检查故障是否已经解决。如果测试请求成功,断路器会切换到 HALFOPEN 状态,允许一个请求发送到该服务或资源。如果这个请求成功,断路器会切换到 CLOSED 状态,否则会重新切换到 OPEN 状态。
  3. 故障率计算:为了判断是否打开断路器,需要收集一定数量的请求数据。在 Resilience4j 中,需要至少填充一个环形缓冲区(Ring Bit Buffer),才能开始计算故障率。环形缓冲区的大小决定了需要多少次请求才能进行故障率的计算。
  4. 环形缓冲区:Resilience4j 使用环形缓冲区来存储请求状态的数据结构,这与 Hystrix 使用的滑动窗口不同。环形缓冲区使用位集合(BitSet)实现,每个位代表一个请求的状态(成功或失败)。环形缓冲区的大小决定了能够存储的请求数量。例如,一个大小为 10 的缓冲区可以存储 1024 个请求状态。
  5. 配置选项:Resilience4j 提供了丰富的配置选项,如故障率阈值、打开状态下的等待时间、半开状态下允许的最大请求数等,开发者可以根据需求进行灵活配置。
通过上述原理,Resilience4j 的 CircuitBreaker 能够有效地保护分布式系统免受故障的影响,提高系统的可用性和健壮性。

2.代码工程

实验目的

利用Circuit Breaker实现接口熔断和自动恢复

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud-demo</artifactId>
        <groupId>com.et</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-cloud-circuit-breaker</artifactId>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>
    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
    </dependencies>
</project>

controller

注解实现

关键组件
  1. name

    • 指定断路器实例的名称。这个名称应该与配置文件中定义的实例名称匹配。系统会根据这个名称查找断路器的配置设置。
  2. fallbackMethod

    • 指定当断路器打开或注解的方法抛出异常时调用的备用方法。备用方法应具有与原始方法相同的返回类型,并且可以选择接受相同的参数,最后还可以接受一个 Throwable 参数以捕获异常。
断路器状态
  • 关闭(Closed):断路器允许调用服务并监控结果。如果失败率超过配置的阈值,断路器将转为打开状态。

  • 打开(Open):断路器会短路对服务的调用,立即返回失败或调用备用方法。经过配置的等待时间后,断路器将转为半开状态。

  • 半开(Half-Open):断路器允许有限数量的测试调用。如果这些调用成功,断路器将转回关闭状态;如果失败,则返回到打开状态。

package com.et.controller;

import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;

@RestController
@RequestMapping("/annotation")
public class MyController {

    private final io.github.resilience4j.circuitbreaker.CircuitBreaker circuitBreaker;
    private boolean simulateFailure = true;

    public MyController(CircuitBreakerRegistry circuitBreakerRegistry) {
        this.circuitBreaker = circuitBreakerRegistry.circuitBreaker("order-service");
    }

    @GetMapping("/my-service")
    @CircuitBreaker(name = "order-service", fallbackMethod = "fallbackMethod")
    public String myService() {
        System.out.println("Circuit Breaker State: " + circuitBreaker.getState());
        System.out.println("Circuit Breaker name: " + circuitBreaker.getName());
        System.out.println("Circuit Breaker config: " + circuitBreaker.getCircuitBreakerConfig().toString());
        if (simulateFailure) {
            throw new RuntimeException("Simulated failure");
        }
        return "Service is up";
    }

  
    public String fallbackMethod(Throwable throwable) {
        if (throwable instanceof CallNotPermittedException) {
            return "Circuit Breaker is OPEN, request not permitted";
        }
        return "Fallback response";
    }

    @GetMapping("/toggle-failure")
    public String toggleFailure() {
        simulateFailure = !simulateFailure;
        return "Failure simulation is now " + (simulateFailure ? "ON" : "OFF");
    }
}
自定义实现
package com.et.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.circuitbreaker.CallNotPermittedException;

import java.util.function.Supplier;

@RestController
public class DemoController {

    private final CircuitBreaker circuitBreaker;
    private boolean simulateFailure = true;

    public DemoController(CircuitBreakerRegistry circuitBreakerRegistry) {
        
        this.circuitBreaker = circuitBreakerRegistry.circuitBreaker("myCircuitBreaker");
    }
    @GetMapping("/my-service")
    public String myService() {
        System.out.println("Circuit Breaker State: " + circuitBreaker.getState());

        Supplier<String> decoratedSupplier = CircuitBreaker.decorateSupplier(circuitBreaker, () -> {
            if (simulateFailure) {
                throw new RuntimeException("Simulated failure");
            }
            return "Service is up";
        });

        try {
            return decoratedSupplier.get();
        } catch (CallNotPermittedException e) {
            return "Circuit Breaker is OPEN, request not permitted";
        } catch (Exception e) {
            return "Fallback response";
        }
    }

    @GetMapping("/toggle-failure")
    public String toggleFailure() {
        simulateFailure = !simulateFailure;
        return "Failure simulation is now " + (simulateFailure ? "ON" : "OFF");
    }
}

配置文件

resilience4j.circuitbreaker:
  instances:
    myCircuitBreaker:
      slidingWindowSize: 10
      failureRateThreshold: 50
      waitDurationInOpenState: 30s
      permittedNumberOfCallsInHalfOpenState: 3


    order-service:
      sliding-window-type: COUNT_BASED
      failure-rate-threshold: 10
      minimum-number-of-calls: 5
      automatic-transition-from-open-to-half-open-enabled: true
      wait-duration-in-open-state: 5s
      permitted-number-of-calls-in-half-open-state: 3
      sliding-window-size: 10
      register-health-indicator: true

myCircuitBreaker

  • slidingWindowSize: 10: This sets the size of the sliding window, which is used to record the outcome of calls. In this case, it records the last 10 calls.

  • failureRateThreshold: 50: This sets the failure rate threshold as a percentage. If the failure rate is equal to or greater than 50%, the circuit breaker transitions to the open state.

  • waitDurationInOpenState: 30s: This specifies the time that the circuit breaker should stay open before transitioning to half-open. Here, it is set to 30 seconds.

  • permittedNumberOfCallsInHalfOpenState: 3: This sets the number of calls that are allowed to pass through when the circuit breaker is in the half-open state. If these calls are successful, the circuit breaker transitions back to closed.

order-service

  • sliding-window-type: COUNT_BASED: This specifies the type of sliding window. COUNT_BASED means the window is based on a fixed number of calls.

  • failure-rate-threshold: 10: This sets a lower failure rate threshold of 10%. If the failure rate reaches this level, the circuit breaker will open.

  • minimum-number-of-calls: 5: This sets the minimum number of calls that must be recorded before the failure rate can be calculated. This prevents the circuit breaker from opening prematurely.

  • automatic-transition-from-open-to-half-open-enabled: true: This enables automatic transition from open to half-open state after the wait duration has elapsed.

  • wait-duration-in-open-state: 5s: This specifies a shorter wait duration of 5 seconds for the circuit breaker to stay open before transitioning to half-open.

  • permitted-number-of-calls-in-half-open-state: 3: Similar to myCircuitBreaker, this sets the number of test calls allowed in the half-open state.

  • sliding-window-size: 10: This sets the sliding window size to 10 calls, similar to myCircuitBreaker.

  • register-health-indicator: true: This enables the registration of a health indicator for the circuit breaker, which can be used for monitoring purposes.

启动类

Spring AOP在使用注解风格的切面时,需要AspectJ的支持,确保在Spring Boot应用中启用了AOP支持,并启用@EnableAspectJAutoProxy注解
package com.et;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@SpringBootApplication
@EnableAspectJAutoProxy
public class DemoApplication {



    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }


}

3.测试

启动应用程序

非注解方式

  1. 初始请求:访问 /my-service,确保在 simulateFailure 为 true 时抛出异常。
  2. 多次失败请求:连续多次访问 /my-service,以达到失败率阈值,触发断路器进入 OPEN 状态。
  3. 检查状态:在控制台中观察断路器状态输出,确保状态变为 OPEN
  4. 访问 /toggle-failure:切换 simulateFailure 为 false
  5. 等待并测试:等待 waitDurationInOpenState 时间后,再次访问 /my-service,观察断路器状态变化和请求结果。

注解方式

  1. 初始请求:访问 /annotation/my-service,确保在 simulateFailure 为 true 时抛出异常。
  2. 多次失败请求:连续多次访问 /annotation/my-service,以达到失败率阈值,触发断路器进入 OPEN 状态。
  3. 检查状态:在控制台中观察断路器状态输出,确保状态变为 OPEN
  4. 访问 /annotation/toggle-failure:切换 simulateFailure 为 false
  5. 等待并测试:等待 waitDurationInOpenState 时间后,再次访问 /annotation/my-service,观察断路器状态变化和请求结果。

4.引用

正文到此结束
Loading...