10.1 Resilience4 简介
Resilience4j 是 Spring Cloud Greenwich 版推荐的容错解决方案。 可能成为主流的解决方案,因为 Hystrix 的公司 Netflix 不再更新, Resilience4j 是受 Hystrix 启发设计出来的 ;相比 Hystrix,Resilience4j 专门为 Java 8 以及函数式编程而设计,更加轻量级,没有任何外部依赖性,除了 resilience4j 自身的依赖,不需要其他依赖
- Resilience4j 主要提供功能
(1)断路器
(2)限流
(3)基于信号量的隔离
(4)缓存
(5)请求限时
(6)请求重试
10.2 Resilience4j 在 JavaSe基本用法
搭建环境
- (1)创建一个普通的 maven 项目 resilience4j 作为 javaboycloud 项目的 module。并添加 junit 依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
10.2.1 断路器
Resilience4j 提供了很多功能,不同功能对应不同依赖,按需添加
- (1)引入 resilience4j-circuitbreaker 依赖
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-circuitbreaker</artifactId>
<version>1.5.0</version>
</dependency>
(2)在 test 目录,创建一个包路径 org.javaboy.resilience4j,新建类 Resilience4jTest,
获取一个 CircuitBreakRegistry 实例,可以调用 ofDefault 方法,也可以自定义。
一个正常执行的例子:
@Test
public void test1(){
// 获取一个 CircuitBreakerRegistry 实例,可以 ofDefaults 来获取 CircuitBreakerRegistr 实例,也可以自定义属性
CircuitBreakerRegistry registry = CircuitBreakerRegistry.ofDefaults();
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
//故障率阈值百分比,超过这个阈值,断路器就会打开
.failureRateThreshold(50)
//断路器保持打开的时间,在到达设置的时间后,断路器会进入 half open 状态
.waitDurationInOpenState(Duration.ofMillis(1000))
// 当断路器处于 half open 状态,环形缓冲区的大小
.ringBufferSizeInHalfOpenState(2)
//当断路器关闭时,环形缓冲区的大小
.ringBufferSizeInClosedState(2)
.build();
CircuitBreakerRegistry r1 = CircuitBreakerRegistry.of(config);
//第一种写法
CircuitBreaker cb1 = r1.circuitBreaker("javaboy");
CircuitBreaker cb2 = r1.circuitBreaker("javaboy2", config);
CheckedFunction0<String> supplier = CircuitBreaker.decorateCheckedSupplier(cb1, () -> "Hello resilience4j");
Try<String> result = Try.of(supplier)
.map(v -> v + "hello world");
System.out.println(result.isSuccess());
System.out.println(result.get());
}
运行结果:
一个出异常的断路器
@Test
public void test2(){
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
//故障率阈值百分比,超过这个阈值,断路器就会打开,默认也是 50%
.failureRateThreshold(50)
//断路器保持打开的时间,在到达设置的时间后,断路器会进入 half open 状态
.waitDurationInOpenState(Duration.ofMillis(1000))
// 当断路器处于 half open 状态,唤醒缓冲区的大小
.ringBufferSizeInClosedState(2)
.build();
CircuitBreakerRegistry r1 = CircuitBreakerRegistry.of(config);
CircuitBreaker cb1 = r1.circuitBreaker("javaboy");
//获取断路器状态
System.out.println(cb1.getState());//CLOSED
//第一条数据
cb1.onError(0, TimeUnit.SECONDS,new RuntimeException());
//获取断路器状态
System.out.println(cb1.getState());//CLOSED
//第二条数据
cb1.onSuccess(0,TimeUnit.SECONDS);
//获取断路器状态
System.out.println(cb1.getState());//OPEN,因为一共两次,其中一共错误,故障率到达 50%,断路器开启,不在向下执行
CheckedFunction0<String> supplier = CircuitBreaker.decorateCheckedSupplier(cb1, () -> "Hello resilience4j");
//有 Try.of 也有 Try.run
Try<String> result = Try.of(supplier)
.map(v -> v + "hello world");
System.out.println(result.isSuccess());//false
System.out.println(result.get());
}
运行结果:
.ringBufferSizeInClosedState(2)
表示当有两条数据时才会统计故障率,所以,下面的手动故障,至少调用两次 onError 或者一次 onError 、一次 onSuccess 断路器才会打开。
10.2.2 限流
Resilience4j 中的限流 RateLimiter 本身和前面的断路器很像,有 RateLimiterRegistry、RateLimiterConfig.
- (1)添加依赖
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-ratelimiter</artifactId>
<version>1.5.0</version>
</dependency>
- (2)测试
@Test
public void test3(){
// 每个周期为 1 秒(limitRefreshPeriod(Duration.ofMillis(1000)))
RateLimiterConfig config = RateLimiterConfig.custom().limitRefreshPeriod(Duration.ofMillis(1000))
// 为了测试方便,只允许两个请求,即每秒两个请求
.limitForPeriod(2)
// 限流之后的冷却时间
.timeoutDuration(Duration.ofMillis(1000))
.build();
RateLimiter rateLimiter = RateLimiter.of("javaboy", config);
CheckedRunnable checkedRunnable = RateLimiter.decorateCheckedRunnable(rateLimiter, () -> {
System.out.println(new Date());
});
// 调用 Java 8 流式编程的 run 方法
//一共调用四次,因为每秒只能处理两个,所以应该分两秒打印结果
Try.run(checkedRunnable)
.andThenTry(checkedRunnable)
.andThenTry(checkedRunnable)
.andThenTry(checkedRunnable)
//失败了,打印错误
.onFailure(t-> System.out.println(t.getMessage()));
}
运行结果:
如果把.limitForPeriod(4)
参数改为4,那么结果在一秒打印:
10.2.3 请求重试
Spring 家族中使用的比较多的时 Spring-retry,Resilience4j 中也提供了重试的功能,而且 Resilience4j 的重试更加轻量级,而且可以根据结果来决定是否要进行重试。一句话:Resilience4j 更好用一些。
- (1)添加依赖
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-retry</artifactId>
<version>1.5.0</version>
</dependency>
- (2)测试:
@Test
public void test4(){
RetryConfig config = RetryConfig.custom()
// 最大重试次数 2 次
.maxAttempts(4)
// 重试时间间隔
.waitDuration(Duration.ofMillis(500))
// 发生指定异常重试
.retryExceptions(RuntimeException.class)
.build();
Retry retry = Retry.of("javaboy", config);
// 开启重试功能之后,run 方法如果抛异常,会自动触发重试功能
Retry.decorateRunnable(retry,new Runnable(){
int count = 0;
@Override
public void run() {
System.out.println(count);
if(count++ < 3){
throw new RuntimeException();
}
System.out.println("retry");
}
}).run();
}
运行结果:
这里的重试,是以面向切面的方式来代理 run() 方法,count 的属性时会递增的,重试了两次还不成功,就抛出异常了,即代理不重试了,抛出异常;如果把 .maxAttempts(4)
改成 4 次,那第四次 count = 3,那么就会不走 if 判断结果就成功了:
这就是重试功能,本来应该报错的,经过重试后,返回了正确的结果