1.什么是Spring Cloud Circuit Breaker?
Spring Cloud Circuit breaker提供了一个跨越不同断路器实现的抽象。它提供了一个一致的API,可以在你的应用程序中使用,允许你的开发者选择最适合你的应用程序需求的断路器实现。
它还支持的实现有如下几种
- Resilience4j
- Hystrix
- Sentinel
- Spring Retry
Resilience4j概述
Resilience4J 是一个针对 Java 8 应用程序的轻量级容错和弹性库。它设计用于在分布式系统中的服务之间提供弹性和容错性。Resilience4J 的名字来源于它提供的核心功能,即让系统(服务)能够“弹性”(resilient)地应对各种失败情况,包括网络问题、第三方服务故障等。 Resilience4J 提供了以下功能:
- 断路器(Circuit Breaker):当检测到服务异常或超时,断路器会打开,阻止进一步的请求发送到该服务。一段时间后(通常是秒级),断路器会进入半开状态,允许一个测试请求通过以检查服务是否恢复。如果请求成功,断路器关闭;如果失败,断路器会再次打开。
- 限流(Rate Limiter):限制进入系统的请求速率,防止系统过载。这可以通过令牌桶算法或滑动窗口算法实现。
- 隔离(Isolation):通过信号量或线程池隔离不同的服务调用,防止一个服务的失败影响到其他服务。
- 超时(Timeouts):为服务调用设置超时时间,超过时间后会触发超时异常。
- 重试(Retry):在遇到特定异常时自动重试服务调用,可以配置重试次数和间隔。
- 缓存(Caching):提供缓存机制,以避免重复执行计算密集型或远程调用。
Resilience4j 的 CircuitBreaker 实现原理如下
- 断路器的状态:CircuitBreaker 具有三种正常状态:CLOSED(关闭)、OPEN(打开)和 HALFOPEN(半开),以及两个特殊状态:DISABLED(禁用)和 FORCEDOPEN(强制打开)。这些状态通过有限状态机进行管理。
- 打开和关闭逻辑:当被保护的服务或资源发生故障或长时间不可用时,断路器会迅速切换到 OPEN 状态,阻止更多的请求发送到该服务或资源。在 OPEN 状态下,系统会定期发送测试请求,以检查故障是否已经解决。如果测试请求成功,断路器会切换到 HALFOPEN 状态,允许一个请求发送到该服务或资源。如果这个请求成功,断路器会切换到 CLOSED 状态,否则会重新切换到 OPEN 状态。
- 故障率计算:为了判断是否打开断路器,需要收集一定数量的请求数据。在 Resilience4j 中,需要至少填充一个环形缓冲区(Ring Bit Buffer),才能开始计算故障率。环形缓冲区的大小决定了需要多少次请求才能进行故障率的计算。
- 环形缓冲区:Resilience4j 使用环形缓冲区来存储请求状态的数据结构,这与 Hystrix 使用的滑动窗口不同。环形缓冲区使用位集合(BitSet)实现,每个位代表一个请求的状态(成功或失败)。环形缓冲区的大小决定了能够存储的请求数量。例如,一个大小为 10 的缓冲区可以存储 1024 个请求状态。
- 配置选项: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
注解实现
关键组件
-
name
:- 指定断路器实例的名称。这个名称应该与配置文件中定义的实例名称匹配。系统会根据这个名称查找断路器的配置设置。
-
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: 10failureRateThreshold: 50waitDurationInOpenState: 30spermittedNumberOfCallsInHalfOpenState: 3order-service:sliding-window-type: COUNT_BASEDfailure-rate-threshold: 10minimum-number-of-calls: 5automatic-transition-from-open-to-half-open-enabled: truewait-duration-in-open-state: 5spermitted-number-of-calls-in-half-open-state: 3sliding-window-size: 10register-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 tomyCircuitBreaker
, 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 tomyCircuitBreaker
. -
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);}}
以上只是一些关键代码,所有代码请参见下面代码仓库
代码仓库
- https://github.com/Harries/springcloud-demo(spring cloud Circuit Breaker )
3.测试
启动应用程序
非注解方式
- 初始请求:访问
/my-service
,确保在simulateFailure
为true
时抛出异常。 - 多次失败请求:连续多次访问
/my-service
,以达到失败率阈值,触发断路器进入OPEN
状态。 - 检查状态:在控制台中观察断路器状态输出,确保状态变为
OPEN
。 - 访问
/toggle-failure
:切换simulateFailure
为false
。 - 等待并测试:等待
waitDurationInOpenState
时间后,再次访问/my-service
,观察断路器状态变化和请求结果。
注解方式
- 初始请求:访问 /annotation
/my-service
,确保在simulateFailure
为true
时抛出异常。 - 多次失败请求:连续多次访问 /annotation
/my-service
,以达到失败率阈值,触发断路器进入OPEN
状态。 - 检查状态:在控制台中观察断路器状态输出,确保状态变为
OPEN
。 - 访问 /annotation
/toggle-failure
:切换simulateFailure
为false
。 - 等待并测试:等待
waitDurationInOpenState
时间后,再次访问 /annotation/my-service
,观察断路器状态变化和请求结果。
4.引用
- Spring Cloud Circuit Breaker
- Spring Cloud Circuit Breaker快速入门Demo | Harries Blog™