1、介绍
Spring Cloud Circuit breaker provides an abstraction across different circuit breaker implementations. It provides a consistent API to use in your applications allowing you the developer to choose the circuit breaker implementation that best fits your needs for your app.
Spring Cloud断路器提供了一个跨不同断路器的抽象实现。它提供了在您的应用程序中使用的一致的API,允许您的开发人员选择最适合您的应用程序需要的断路器实现。
CircuitBreaker通过具有三种正常状态的有限状态机实现:CLOSED,OPEN和HALF_OPEN以及两个特殊状态DISABLED和FORCED_OPEN。当熔断器关闭时,所有的请求都会通过熔断器。如果失败率超过设定的阈值,熔断器就会从关闭状态转换到打开状态,这时所有的请求都会被拒绝。当经过一段时间后,熔断器会从打开状态转换到半开状态,这时仅有一定数量的请求会被放入,并重新计算失败率,如果失败率超过阈值,则变为打开状态,如果失败率低于阈值,则变为关闭状态。
![断路器状态机 断路器状态机](https://i-blog.csdnimg.cn/blog_migrate/748c6811218c2adf1aca3a8ad77284e6.png)
支持断路器实现有以下几种
- Netfix Hystrix (Spring Cloud官方已经不予支持)
- Resilience4J (支持)
- Sentinel
- Spring Retry
2、快速开始
pom.xml
maven依赖配置文件,如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.4</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.mindasoft</groupId> <artifactId>spring-cloud-circuitbreaker-consumer</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-cloud-circuitbreaker-consumer</name> <description>Demo project for Spring Boot</description> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>2021.0.1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dep 西藏党政培训 www.swuplganxun.com endency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>nexus-aliyun</id> <name>Nexus aliyun</name> <url>https://maven.aliyun.com/repository/public</url> <layout>default</layout> <snapshots> <enabled>false</enabled> </snapshots> <releases> <enabled>true</enabled> </releases> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>nexus-aliyun</id> <name>Nexus aliyun</name> <url>https://maven.aliyun.com/repository/public</url> <snapshots> <enabled>false</enabled> </snapshots> <releases> <enabled>true</enabled> </releases> </pluginRepository> </pluginRepositories></project>
比上个项目多了如下配置:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId> </dependency>
使用resilience4j 实现断路器。
application.properties
server.port=9004# 服务注册中心地址eureka.client.service-url.defaultZone=http://localhost:9000/eureka/# 服务名称spring.application.name=circuitbreaker-customer
SpringCloudCircuitBreakerConsumerApplication
package com.mindasoft;import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;import io.github.resilience4j.timelimiter.TimeLimiterConfig;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JCircuitBreakerFactory;import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JConfigBuilder;import org.springframework.cloud.client.circuitbreaker.Customizer;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.client.loadbalancer.LoadBalanced;import org.springframework.context.annotation.Bean;import org.springframework.web.client.RestTemplate;import java.time.Duration;@EnableDiscoveryClient // Eureka Discovery Client 标识@SpringBootApplicationpublic class SpringCloudCircuitBreakerConsumerApplication { // 开启负载均衡的功能 @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } @Bean public Customizer<Resilience4JCircuitBreakerFactory> defaultCustomizer() { return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id) .timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(3)).build()) .circuitBreakerConfig(CircuitBreakerConfig.ofDefaults()) .build()); } public static void main(String[] args) { SpringApplication.run(SpringCloudCircuitBreakerConsumerApplication.class, args); }}
ConsumerController
package com.mindasoft.consumer;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.client.RestTemplate;/** * Created by huangmin on 2017/11/10 14:50. */@RestControllerpublic class ConsumerController { private static final Logger LOGGER = LoggerFactory.getLogger(ConsumerController.class); @Autowired private RestTemplate restTemplate; // HTTP 访问操作类 @Autowired private CircuitBreakerFactory circuitBreakerFactory; @RequestMapping("/hello") public String hello() { String providerMsg = circuitBreakerFactory.create("hello") .run(() -> restTemplate.getForObject("http://PROVIDER-SERVICE/hello", String.class), throwable -> "CircuitBreaker fallback msg"); return "Hello,I'm Customer! msg from provider : <br/><br/> " + providerMsg; }}
启动eureka server和本项目,访问http://127.0.0.1:9004/hello
页面打印如下:
Hello,I'm Customer! msg from provider :CircuitBreaker fallback msg
说明进入到了断路throwable 处理逻辑。
Resilience4J介绍
Resilience4j是一款轻量级,易于使用的容错库,其灵感来自于Netflix Hystrix,但是专为Java 8和函数式编程而设计。轻量级,因为库只使用了Vavr,它没有任何其他外部依赖下。相比之下,Netflix Hystrix对Archaius具有编译依赖性,Archaius具有更多的外部库依赖性,例如Guava和Apache Commons Configuration。
Resilience4j提供了以下的核心模块和拓展模块:
核心模块:
- resilience4j-circuitbreaker: Circuit breaking
- resilience4j-ratelimiter: Rate limiting
- resilience4j-bulkhead: Bulkheading
- resilience4j-retry: Automatic retrying (sync and async)
- resilience4j-cache: Result caching
- resilience4j-timelimiter: Timeout handling
Resilience4j记录请求状态的数据结构和Hystrix不同,Hystrix是使用滑动窗口来进行存储的,而Resilience4j采用的是Ring Bit Buffer(环形缓冲区)。Ring Bit Buffer在内部使用BitSet这样的数据结构来进行存储,BitSet的结构如下图所示:
每一次请求的成功或失败状态只占用一个bit位,与boolean数组相比更节省内存。BitSet使用long[]数组来存储这些数据,意味着16个值(64bit)的数组可以存储1024个调用状态。
计算失败率需要填满环形缓冲区。例如,如果环形缓冲区的大小为10,则必须至少请求满10次,才会进行故障率的计算,如果仅仅请求了9次,即使9个请求都失败,熔断器也不会打开。但是CLOSE状态下的缓冲区大小设置为10并不意味着只会进入10个 请求,在熔断器打开之前的所有请求都会被放入。
当故障率高于设定的阈值时,熔断器状态会从由CLOSE变为OPEN。这时所有的请求都会抛出CallNotPermittedException异常。当经过一段时间后,熔断器的状态会从OPEN变为HALF_OPEN,HALF_OPEN状态下同样会有一个Ring Bit Buffer,用来计算HALF_OPEN状态下的故障率,如果高于配置的阈值,会转换为OPEN,低于阈值则装换为CLOSE。与CLOSE状态下的缓冲区不同的地方在于,HALF_OPEN状态下的缓冲区大小会限制请求数,只有缓冲区大小的请求数会被放入。
除此以外,熔断器还会有两种特殊状态:DISABLED(始终允许访问)和FORCED_OPEN(始终拒绝访问)。这两个状态不会生成熔断器事件(除状态装换外),并且不会记录事件的成功或者失败。退出这两个状态的唯一方法是触发状态转换或者重置熔断器。
熔断器关于线程安全的保证措施有以下几个部分:
- 熔断器的状态使用AtomicReference保存的
- 更新熔断器状态是通过无状态的函数或者原子操作进行的
- 更新事件的状态用synchronized关键字保护
- 意味着同一时间只有一个线程能够修改熔断器状态或者记录事件的状态。