(1). 简介

Circuit Breaker通过具有三种正常状态的有限状态机来实现:CLOSED,OPEN和HALF_OPEN以及两个特殊状态:DISABLED和FORCED_OPEN.
当熔断器关闭(close)时,所有的请求都会通过熔断器.
如果失败率超过设定的阈值,熔断器就会从关闭状态转换到打开状态(open),这时所有的请求都会被拒绝.
当经过一段时间后,熔断器会从打开状态转换到半开状态(half_open),这时仅有一定数量的请求会被放入,并重新计算失败率.
如果失败率超过阈值,则变为打开状态(open),如果失败率低于阈值,则变为关闭状态(close).

(2). CircuitBreaker配置

配置属性 默认值 描述
failureRateThreshold 50 失败请求百分比,超过这个比例,CircuitBreaker就会变成OPEN状态
slowCallDurationThreshold 60000(ms) 慢调用时间,当一个调用慢于这个时间时,会被记录为慢调用
slowCallRateThreshold 100 当慢调用达到这个百分比的时候,CircuitBreaker就会变成OPEN状态
permittedNumberOfCallsInHalfOpenState 10 当CircuitBreaker处于HALF_OPEN状态的时候,允许通过的请求数量
slidingWindowType COUNT_BASED 滑动窗口类型:COUNT_BASED代表是基于计数的滑动窗口,TIME_BASED代表是基于计时的滑动窗口
slidingWindowSize 100 滑动窗口大小,如果配置COUNT_BASED默认值100就代表是最近100个请求,如果配置TIME_BASED代表记录:最近100s的请求.
minimumNumberOfCalls 100 最小请求个数.只有在滑动窗口内,请求个数达到这个个数,才会触发CircuitBreaker对于是否打开断路器的判断
waitDurationInOpenState 60000(ms) 从OPEN状态变成HALF_OPEN状态需要的等待时间
automaticTransitionFromOpenToHalfOpenEnabled false 如果设置为true代表:是否自动从OPEN状态变成HALF_OPEN,即使没有请求过来.
recordExceptions empty 需要记录为失败的异常列表
ignoreExceptions empty 需要忽略的异常列表

(3). CircuitBreaker配置案例

// 1. 所有Exception以及其子类都认为是失败.
// 2. 滑动窗口采用基于计时的,并且记录最近10秒的请求.
// 3. 触发断路器判断必须在10秒内至少有5个请求,在失败比例达到30%以上之后,断路器变为:OPEN.  
// 4. 断路器OPEN之后,在2秒后自动转化为HALF_OPEN.
// 5. 断路器在HALF_OPEN之后,允许通过的请求数量为:3个
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
		// 滑动窗口类型(TIME_BASED:时间 / COUNT_BASED:计数器 )
		.slidingWindowType(SlidingWindowType.TIME_BASED)
		// 滑动窗口大小(记录最近10秒的请求)
		.slidingWindowSize(10)
		// 最小请求个数.只有在滑动窗口内,请求个数达到这个个数,才会触发CircuitBreaker对于是否打开断路器的判断
		.minimumNumberOfCalls(5)
		// 当CircuitBreaker处于HALF_OPEN状态的时候,允许通过的请求数量
		.permittedNumberOfCallsInHalfOpenState(3)
		// 自动从OPEN状态变成HALF_OPEN,即使没有请求过来.
		.automaticTransitionFromOpenToHalfOpenEnabled(true)
		// 从OPEN状态变成HALF_OPEN状态需要的等待2秒
		.waitDurationInOpenState(Duration.ofSeconds(2))
		// 失败率达到30%,CircuitBreaker就会变成OPEN状态
		.failureRateThreshold(30)
		// 所有Exception异常会统计为失败.
		.recordExceptions(Exception.class)
		// 
		.build();

(4). pom.xml

<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-dependencies</artifactId>
			<version>Greenwich.RELEASE</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-dependencies</artifactId>
			<version>2.1.0.RELEASE</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-netflix</artifactId>
			<version>2.1.1.RELEASE</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>

<dependencies>
	 <dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency> 
		<groupId>io.github.resilience4j</groupId>
		<artifactId>resilience4j-spring</artifactId>
		<version>1.7.0</version>
	</dependency>
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<scope>test</scope>
	</dependency>
	
	<dependency>
		<groupId>org.assertj</groupId>
		<artifactId>assertj-core</artifactId>
	</dependency>
	<dependency>
		<groupId>org.mockito</groupId>
		<artifactId>mockito-core</artifactId>
		<scope>test</scope>
	</dependency>
</dependencies>

(5). HelloWorldService

HelloWorldService实际为业务代码

package help.lixin.resilience4j;

import io.vavr.control.Either;
import io.vavr.control.Try;

import java.io.IOException;
import java.util.concurrent.Future;

public interface HelloWorldService {
	
	String returnHelloWorld();

    Future<String> returnHelloWorldFuture();

    Either<HelloWorldException, String> returnEither();

    Try<String> returnTry();

    String returnHelloWorldWithException() throws IOException;

    String returnHelloWorldWithName(String name);

    String returnHelloWorldWithNameWithException(String name) throws IOException;

    void sayHelloWorld();

    void sayHelloWorldWithException() throws IOException;

    void sayHelloWorldWithName(String name);

    void sayHelloWorldWithNameWithException(String name) throws IOException;
}

(6). HelloWorldException

HelloWorldException为自定义的业务异常.

package help.lixin.resilience4j;

public class HelloWorldException extends RuntimeException {

    public HelloWorldException() {
        super("BAM!");
    }

    public HelloWorldException(String message) {
        super(message);
    }
}

(7). CircuitBreakerTest

CircuitBreakerTest单元测试

@Test
public void test() {
	// 1. 所有Exception以及其子类都认为是失败.
	// 2. 滑动窗口采用基于统计的,并且记录最近10个的请求.
	// 3. 触发断路器判断至少有5个请求,在失败比例达到20%以上之后,断路器变为:OPEN.
	// 4. 断路器OPEN之后,在2秒后自动转化为HALF_OPEN.
	// 5. 断路器在HALF_OPEN之后,允许通过的请求数量为:3个
	CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
			// 滑动窗口类型(TIME_BASED:时间 / COUNT_BASED:计数器 )
			.slidingWindowType(SlidingWindowType.COUNT_BASED)
			// 滑动窗口大小(统计最近的10个请求)
			.slidingWindowSize(10)
			// 最小请求个数.只有在滑动窗口内,请求个数达到这个个数,才会触发CircuitBreaker对于是否打开断路器的判断
			.minimumNumberOfCalls(5)
			// 当CircuitBreaker处于HALF_OPEN状态的时候,允许通过的请求数量
			.permittedNumberOfCallsInHalfOpenState(3)
			// 自动从OPEN状态变成HALF_OPEN,即使没有请求过来.
			.automaticTransitionFromOpenToHalfOpenEnabled(true)
			// 从OPEN状态变成HALF_OPEN状态需要的等待2秒
			.waitDurationInOpenState(Duration.ofSeconds(2))
			// 失败率达到30%,CircuitBreaker就会变成OPEN状态
			.failureRateThreshold(20)
			// 所有Exception异常会统计为失败.
			.recordExceptions(Exception.class)
			//
			.build();
	
		CircuitBreakerRegistry registry = CircuitBreakerRegistry.of(circuitBreakerConfig);
		CircuitBreaker circuitBreaker = registry.circuitBreaker("test");

		// 业务代码
		HelloService helloService = new HelloService();
		for (int i = 1; i <= 12; i++) {
			int index = i;
			Supplier<String> supplier = circuitBreaker.decorateSupplier(() -> {
				return helloService.sayHello(index);
			});

			if (i == 6) {
				try {
					TimeUnit.SECONDS.sleep(3);
					System.out.println("****************************************************************");
				} catch (InterruptedException e1) {
				}
			}

			Try.ofSupplier(supplier) //
					.onSuccess(s -> {
						System.out.println(new Date() + "  " + s + "  " + circuitBreaker.getState());
					}).onFailure(e -> {
						System.err.println(new Date() + "  " + e.getMessage() + "  " + circuitBreaker.getState());
					});
		} // end  for
}// end test


// HelloService
class HelloService {
	public String sayHello(int index) {
		if (index < 6 && index % 2 == 0) {
			throw new RuntimeException("fail index->" + index);
		}
		return "success index : " + index;
	}
}

(8). 日志验证

# 请求成功(CircuitBreaker状态为:CLOSE)
Sat May 15 21:49:29 CST 2021  success index : 1  CLOSED
# 请求失败(CircuitBreaker状态为:CLOSE)
Sat May 15 21:49:29 CST 2021  fail index->2  CLOSED
# 请求成功(CircuitBreaker状态为:CLOSE)
Sat May 15 21:49:29 CST 2021  success index : 3  CLOSED
# 请求失败(CircuitBreaker状态为:CLOSE)
Sat May 15 21:49:29 CST 2021  fail index->4  CLOSED

# ************************************************************
# 从第5个请求开始计算失败率CircuitBreaker状态为:OPEN)
Sat May 15 21:49:29 CST 2021  success index : 5  OPEN

# ************************************************************
# 在第6个请求的时候,我让程序休眠了3秒,因为,状态一旦OPEN之后,所有的请求都是会被拒绝的,只有等待2秒后,回归到:HALF_OPEN,才开始接受一部份请求.
# OPEN的时间为:29秒,HALF_OPEN的时间为:32秒
Sat May 15 21:49:32 CST 2021  success index : 6  HALF_OPEN
Sat May 15 21:49:32 CST 2021  success index : 7  HALF_OPEN

# 开始正确接受请求(CircuitBreaker状态为:CLOSE)
Sat May 15 21:49:32 CST 2021  success index : 8  CLOSED
Sat May 15 21:49:32 CST 2021  success index : 9  CLOSED
Sat May 15 21:49:32 CST 2021  success index : 10  CLOSED
Sat May 15 21:49:32 CST 2021  success index : 11  CLOSED
Sat May 15 21:49:32 CST 2021  success index : 12  CLOSED

(8). 总结

熔断与限流的区别在于:熔断之后,还存在半打开状态,同时,熔断会进行降级处理,而限流是直接拒绝请求了,缺少一个智能的半打开.