SpringCloud之在微服务中使用Resilience4J

在上一篇文章我们讲了断路器,又详细的讲解了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

关于上面的配置解释如下:

  1. 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 ,即数值越小,优先级越高;
  2. backends 属性中我们可以配置不同的 Retry 策略,给不同的策略分别取一个名字, retryBackendA 就是一个 Retry 策略的名字。在 Java 代码中,我们将直接通过指定 Retry 策略的名字来使用某一种 Retry 方案
  3. maxRetryAttempts 表示最大重试次数
  4. waitDuration 表示下一次重试等待时间,最小为100 ms
  5. eventConsumerBufferSize 表示重试事件缓冲区大小
  6. enableExponentialBackoff 表示是否开启指数退避抖动算法,当一次调用失败后,如果在相同的时间间隔内发起重试,有可能发生连续的调用失败,因此可以开启指数退避抖动算法;
  7. exponentialBackoffMultiplier 表示时间间隔乘数
  8. enableRandomizedWait 表示下次重试的时间间隔是否随机, enableRandomizedWait 和 enableExponentialBackoff 默认为 false ,并且这两个不可以同时开启
  9. retryExceptionPredicate 类似于我们上文所说的什么样的异常会被认定为请求失败,这里的RecordFailurePredicate是一个自定义的类
  10. retryExceptions 表示需要重试的异常
  11. 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

上面的配置意思,也是非常的简单,上篇文章讲的一样。

  1. backendA 是断路器策略的命名,和 Retry 类似,一会也是通过注解来引用这个策略
  2. ringBufferSizeInClosedState 表示断路器关闭状态下,环形缓冲区的大小
  3. ringBufferSizeInHalfOpenState 表示断路器处于 HalfOpen 状态下,环形缓冲区的大小
  4. waitInterval 表示断路器从 open 切换到 half closed 状态时,需要保持的时间;
  5. failureRateThreshold 表示故障率阈值百分比,超过这个阈值,断路器就会打开
  6. eventConsumerBufferSize 表示事件缓冲区大小
  7. 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();
}

上面这段代码的意思是:

  1. 首先利用 Java 代码创建一个 CircuitBreakerConfig 出来,然后配置一下故障率阈值,等待时间以及环形缓冲区大小等;
  2. 根据第一步创建出来的 CircuitBreakerConfig ,再去创建一个 CircuitBreaker 对象;
  3. 通过 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

这段配置的解释如下:

  1. backendA 在这里依然表示配置的名称,在 Java 代码中,我们将通过指定限流工具的名称来使用某一种限流策略
  2. limitForPeriod 表示请求频次的阈值
  3. limitRefreshPeriodInMillis 表示频次刷新的周期
  4. timeoutInMillis 许可期限的等待时间,默认为5秒
  5. subscribeForEvents 表示开启事件订阅
  6. registerHealthIndicator 表示开启健康监控
  7. 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 的元素,确实非常好用,也是未来处理微服务系统稳定性的一个方向。

源码地址

github

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值