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.引用

相关推荐
工一木子8 分钟前
【Leecode】Leecode刷题之路第42天之接雨水
java·算法·leetcode
Yue1one15 分钟前
I - O (输入-输出) 字节流 字符流 缓冲流
java
w_t_y_y17 分钟前
SQL拦截(二)InnerInterceptor
java
Aliano21720 分钟前
Java的jackson库
java·开发语言
昙鱼43 分钟前
Maven的了解与使用
java·maven
记录学习-python1 小时前
web开发Django+vue3
后端·python·django
北纬39°的风1 小时前
从0开始搭建一个生产级SpringBoot2.0.X项目(十)SpringBoot 集成RabbitMQ
java·spring boot·redis
forestqq1 小时前
设置JAVA以适配华为2288HV2服务器的KVM控制台
java·运维·服务器
LUwantAC1 小时前
Java学习路线:Maven(一)认识Maven
java·学习·maven
勤匠1 小时前
使用 Stream 处理集合数据【Java 1.8 新特性】
java