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 复制代码
<?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 {

    kotlin 复制代码
    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");
    }

    }

自定义实现

kotlin 复制代码
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");
    }
}

配置文件

yaml 复制代码
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注解

typescript 复制代码
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,确保在 simulateFailuretrue 时抛出异常。
  2. 多次失败请求 :连续多次访问 /my-service,以达到失败率阈值,触发断路器进入 OPEN 状态。
  3. 检查状态 :在控制台中观察断路器状态输出,确保状态变为 OPEN
  4. 访问 /toggle-failure :切换 simulateFailurefalse
  5. 等待并测试 :等待 waitDurationInOpenState 时间后,再次访问 /my-service,观察断路器状态变化和请求结果。

注解方式

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

4.引用

相关推荐
橙序员小站1 天前
Java 接入 Pinecone 搭建知识库踩坑实记
java·后端
7哥♡ۣۖᝰꫛꫀꪝۣℋ1 天前
Spring IoC&DI
java·开发语言·mysql
wadesir1 天前
Go语言反射之结构体的深比较(详解reflect.DeepEqual在结构体比较中的应用)
开发语言·后端·golang
即将进化成人机1 天前
springboot项目创建方式
java·spring boot·后端
教练、我想打篮球1 天前
117 javaweb servlet+jsp 项目中修改了 数据库连接配置, 却怎么都不生效
java·servlet·jdbc·jsp
你不是我我1 天前
【Java 开发日记】我们来说一说 Redis IO 多路复用模型
java·开发语言·redis
SadSunset1 天前
(13)复杂查询
java·笔记·架构·mybatis
浩瀚地学1 天前
【Java】ArrayList
java·开发语言·经验分享·笔记
阿杰同学1 天前
Java 设计模式 面试题及答案整理,最新面试题
java·开发语言·设计模式
这样の我1 天前
java 模拟chrome指纹 处理tls extension顺序
java·开发语言·chrome