SpringCloud之在微服务中使用Resilience4J
在上一篇文章我们将了断路器,又详细的讲解了Resilience4J的用法,但是都是基于普通环境下的使用,并没有在微服务中应用。Resilience4J只有在微服务中才能大展身手,那么我们下面就来具体将Resilience4J 运用到我们的微服务中。准备工作首先我们创建一个Resilience4J-SpringBoot的普通maven工程,作为父工程,然后我们在父工程中创建一个eureka的Sp..
在上一篇文章我们讲了断路器,又详细的讲解了Resilience4J的用法,但是都是基于普通环境下的使用,并没有在微服务中应用。Resilience4J只有在微服务中才能大展身手,那么我们下面就来具体将Resilience4J 运用到我们的微服务中。
准备工作
首先我们创建一个Resilience4J-SpringBoot的普通maven工程,作为父工程,然后我们在父工程中创建一个eureka的SpringBoot工程,然后再创建一个provider的工程,将provider注册到eureka注册中心上。这里的步骤非常的简单,如果忘记了,可以参考我其它的文章。
创建好后,我们在provider中提供一个/hello
接口:
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(String name) {
String s = "hello " + name + " !";
System.out.println(s+">>>>>"+new Date());
int i = 1 / 0; //这里会报错
return s;
}
}
我们故意在这个接口中设置了一个错误,那么consumer调用这个接口的时候一定会报错的。这样就方便我们利用Resilience4J 去测试重试等功能。
接下来我们创建一个consumer的SpringBoot工程,配置也和provider一样,创建好了后,需要将consumer 注册到eureka的注册中心上,这里步骤就不再阐述,非常的简单,可以参考我之前的其它文章。
如果不清楚请阅读这里 SpringCloud之服务注册与消费
这样我们的准备工作就完成了。
重试功能Retry
我们的项目一般在开发环境下都比较稳定,不会出什么问题,但是项目上线后,就容易出现问题,比如网络的波动造成服务调用失败,在生产环境中,网络原因造成服务调用失败是不可以忽略的,那么我们一定要进行请求重试。
那么请求重试有两种方法,一种就是Spring Aop式,另一种就是编程式,下面我们就来用这两种方法来实现请求重试。
首先我们consumer添加依赖,依赖如下:
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>0.14.1</version>
</dependency>
Aop式
在添加好依赖后我们,我们在consumer中的application.yml中添加如下配置:
resilience4j.retry:
retryAspectOrder: 399
backends:
retryBackendA:
maxRetryAttempts: 3
waitDuration: 600
eventConsumerBufferSize: 1
enableExponentialBackoff: true
exponentialBackoffMultiplier: 2
enableRandomizedWait: false
randomizedWaitFactor: 2
retryExceptionPredicate: cn.com.scitc.RecordFailurePredicate
retryExceptions:
- java.io.IOException
ignoreExceptions:
- cn.com.scitc.exception.IgnoredException
关于上面的配置解释如下:
- retryAspectOrder 表示 Retry 的一个优先级。默认情况下, Retry 的优先级高于 bulkhead 、 Circuit breaker 以及 rateLimiter ,即 Retry 会先于另外三个执行。 Retry、 bulkhead 、 Circuit breaker 以及 rateLimiter 的优先级数值默认分别是 Integer.MAX_VALUE-3、Integer.MAX_VALUE-2、Integer.MAX_VALUE-1 以及 Integer.MAX_VALUE ,即数值越小,优先级越高;
- backends 属性中我们可以配置不同的 Retry 策略,给不同的策略分别取一个名字, retryBackendA 就是一个 Retry 策略的名字。在 Java 代码中,我们将直接通过指定 Retry 策略的名字来使用某一种 Retry 方案
- maxRetryAttempts 表示最大重试次数
- waitDuration 表示下一次重试等待时间,最小为100 ms
- eventConsumerBufferSize 表示重试事件缓冲区大小
- enableExponentialBackoff 表示是否开启指数退避抖动算法,当一次调用失败后,如果在相同的时间间隔内发起重试,有可能发生连续的调用失败,因此可以开启指数退避抖动算法;
- exponentialBackoffMultiplier 表示时间间隔乘数
- enableRandomizedWait 表示下次重试的时间间隔是否随机, enableRandomizedWait 和 enableExponentialBackoff 默认为 false ,并且这两个不可以同时开启
- retryExceptionPredicate 类似于我们上文所说的什么样的异常会被认定为请求失败,这里的RecordFailurePredicate是一个自定义的类
- retryExceptions 表示需要重试的异常
- ignoreExceptions 表示忽略的异常。
RecordFailurePredicate的定义如下:
public class RecordFailurePredicate implements Predicate<Throwable> {
@Override
public boolean test(Throwable throwable) {
return true;
}
}
这里test方法默认是返回false的为了方便我们这里返回true,我们还自定义了一个异常IgnoredException
public class IgnoredException extends Exception {
}
那么接下来我们还需要在 启动类中配置一个RestTemplate
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run( ConsumerApplication.class, args );
}
@LoadBalanced
@Bean
RestTemplate restTemplate () {
return new RestTemplate();
}
}
我们还需要一个HelloService来进行调用:
@Service
@Retry(name = "retryBackendA")
public class HelloService {
@Autowired
RestTemplate restTemplate;
public String hello(String name) {
return restTemplate.getForObject("http://provider/hello?name={1}", String.class, name);
}
}
然后在consumer中定义一个HelloController
@RestController
public class HelloController {
@Autowired
HelloService helloService;
@GetMapping("/hello")
public String hello(String name) {
return helloService.hello( name );
}
}
然后我们通过postman 工具访问http://localhost:5001/hello?name=技术无止境
结果如下:
报错了,同时我们看到我们的provider的控制台:
报错了,报错的那行就是我们的 int i = 1 / 0
,当报错后,我们的服务也自带的重试请求:
这里就是我们的服务请求重试了。
编程式
下面式编程式的使用方式:
@GetMapping("/hello2")
public String hello2(String name) {
RetryConfig config = RetryConfig.custom()
.maxAttempts(3)
.waitDuration( Duration.ofMillis(5000))
.build();
Retry retry = Retry.of("id", config);
Try<String> result = Try.ofSupplier(Retry.decorateSupplier(
retry, ()-> helloService.hello(name)
));
return result.get();
}
CircuitBreaker
Resilience4J 断路器使用也是一样的,也是分为Aop式和编程式
Aop式
使用 Aop 式我们继续在application.yml添加如下依赖 :
resilience4j.circuitbreaker:
backends:
backendA:
ringBufferSizeInClosedState: 5
ringBufferSizeInHalfOpenState: 3
waitInterval: 5000
failureRateThreshold: 50
eventConsumerBufferSize: 10
registerHealthIndicator: true
recordFailurePredicate: cn.com.scitc.RecordFailurePredicate
recordExceptions:
- org.springframework.web.client.HttpServerErrorException
ignoreExceptions:
- org.springframework.web.client.HttpClientErrorException
上面的配置意思,也是非常的简单,上篇文章讲的一样。
- backendA 是断路器策略的命名,和 Retry 类似,一会也是通过注解来引用这个策略
- ringBufferSizeInClosedState 表示断路器关闭状态下,环形缓冲区的大小
- ringBufferSizeInHalfOpenState 表示断路器处于 HalfOpen 状态下,环形缓冲区的大小
- waitInterval 表示断路器从 open 切换到 half closed 状态时,需要保持的时间;
- failureRateThreshold 表示故障率阈值百分比,超过这个阈值,断路器就会打开
- eventConsumerBufferSize 表示事件缓冲区大小
- registerHealthIndicator 表示开启健康检测
和 Retry 类似,在 Circuit Breaker 中,我们也可以通过 circuitBreakerAspectOrder 属性来修改 Circuit Breaker 的执行优先级。
配置完成后,接下来我们来定义一个名为 HelloServiceCircuitBreaker 的类,在这个类中,来定义服务请求方法:
@Service
@CircuitBreaker(name = "backendA")
public class HelloServiceCircuitBreaker {
@Autowired
RestTemplate restTemplate;
public String hello(String name) {
return restTemplate.getForObject("http://provider/hello?name={1}", String.class, name);
}
}
这里通过 @CircuitBreaker 注解来启用断路器。最后,我们在 UseHelloController 中调用这个方法即可。
但是这种写法有一个问题,就是没法进行服务容错降级,如果希望进行服务容错降级,那么还是需要我们上篇文章提到的通过编程实现断路器功能。
编程式
通过编程实现断路器功能,就不再需要 application.yml 中的配置了,也不需要在类上添加 @CircuitBreaker(name = “backendA”) 注解,所有的相关配置都是在 Java 代码中完成,和上篇文章基本一样,如下:
public String hello2(String name) {
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofMillis(1000))
.ringBufferSizeInHalfOpenState(20)
.ringBufferSizeInClosedState(20)
.build();
io.github.resilience4j.circuitbreaker.CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("backendA", circuitBreakerConfig);
Try<String> supplier = Try.ofSupplier(io.github.resilience4j.circuitbreaker.CircuitBreaker
.decorateSupplier(circuitBreaker,
() -> restTemplate.getForObject("http://provider/hello?name={1}", String.class, name)))
.recover(Exception.class, "有异常,访问失败!");
return supplier.get();
}
上面这段代码的意思是:
- 首先利用 Java 代码创建一个 CircuitBreakerConfig 出来,然后配置一下故障率阈值,等待时间以及环形缓冲区大小等;
- 根据第一步创建出来的 CircuitBreakerConfig ,再去创建一个 CircuitBreaker 对象;
- 通过 Try.ofSupplier 方法去发送一个请求,如果请求抛出异常,则在 recover 方法中进行服务降级处理,recover 可以写多个。
最后,在 UseHelloController 中调用这里的 hello2 方法去访问 provider 中的接口。接口调用失败后, consumer 中自动进行服务降级,最终返回字符串为 有异常,访问失败!
运行结果如下:
限流
下面我们来讲一下 Resilience4J 中的限流工具 RateLimiter,和上面的工具一样,也有两种使用方式 分别是Aop式 和 编程式
Aop式
下面我们就来配置Aop式的限流,我们在consumer的application.yml进行配置:
resilience4j.ratelimiter:
limiters:
backendA:
limitForPeriod: 1
limitRefreshPeriodInMillis: 5000
timeoutInMillis: 5000
subscribeForEvents: true
registerHealthIndicator: true
eventConsumerBufferSize: 100
这段配置的解释如下:
- backendA 在这里依然表示配置的名称,在 Java 代码中,我们将通过指定限流工具的名称来使用某一种限流策略
- limitForPeriod 表示请求频次的阈值
- limitRefreshPeriodInMillis 表示频次刷新的周期
- timeoutInMillis 许可期限的等待时间,默认为5秒
- subscribeForEvents 表示开启事件订阅
- registerHealthIndicator 表示开启健康监控
- eventConsumerBufferSize 表示事件缓冲区大小
配置完成后,创建一个 HelloServiceRateLimiter 类,内容如下:
@Service
@RateLimiter(name = "backendA")
public class HelloServiceRateLimiter {
@Autowired
RestTemplate restTemplate;
public String hello(String name) {
return restTemplate.getForObject("http://provider/hello?name={1}", String.class, name);
}
}
然后我们在UserController中调用方法:
@GetMapping("/rl")
public void rateLimiter(String name) {
for (int i = 0; i < 5; i++) {
String hello = helloServiceRateLimiter.hello(name);
}
}
编程式
下面我们通过编程式来实现限流:
public void hello2(String name) {
RateLimiterConfig config = RateLimiterConfig.custom()
.limitRefreshPeriod(Duration.ofMillis(5000))
.limitForPeriod(1)
.timeoutDuration(Duration.ofMillis(6000))
.build();
RateLimiterRegistry rateLimiterRegistry = RateLimiterRegistry.of(config);
RateLimiter rateLimiter = RateLimiter.of("backendB", config);
Supplier<String> supplier = RateLimiter.decorateSupplier(rateLimiter, () ->
restTemplate.getForObject("http://provider/hello?name={1}", String.class, name)
);
for (int i = 0; i < 5; i++) {
Try<String> aTry = Try.ofSupplier(supplier);
System.out.println(aTry.get());
}
}
然后我们在UserController中调用方法:
@GetMapping("/r2")
public void rateLimiter2(String name) {
helloServiceRateLimiter.hello2(name);
}
总结
本文主要向大家介绍了 Resilience4j 在微服务中的应用。相对于 Hystrix ,Resilience4j 更加轻便简洁,而且到处充满了 JDK8 的元素,确实非常好用,也是未来处理微服务系统稳定性的一个方向。
源码地址
更多推荐
所有评论(0)