0


重试框架入门:Spring-Retry&Guava-Retry

前言

在日常工作中,随着业务日渐庞大,不可避免的涉及到调用远程服务,但是远程服务的健壮性和网络稳定性都是不可控因素,因此,我们需要考虑合适的重试机制去处理这些问题,最基础的方式就是手动重试,侵入业务代码去处理,再高端一点的通过切面去处理,较为优雅的实现重试,下面,介绍两个重试框架,只需要配置好重启策略及重试任务,即可使用。

重试任务

这里只是模拟传参、相应及异常,具体任务需对应业务

  1. package com.example.test.MessageRetry;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.apache.commons.lang3.RandomUtils;
  4. import org.springframework.remoting.RemoteAccessException;
  5. @Slf4j
  6. public class RetryTask {
  7. /**
  8. * 重试方法
  9. * @param param
  10. * @return
  11. */
  12. public static boolean retryTask(String param){
  13. log.info("请求参数:{}",param);
  14. int i = RandomUtils.nextInt(0,15);
  15. log.info("随机数:{}",i);
  16. if(i == 0){
  17. log.error("参数异常");
  18. throw new IllegalArgumentException("参数异常");
  19. }else if(i > 10){
  20. log.error("访问异常");
  21. throw new RemoteAccessException("访问异常");
  22. }else if(i % 2 == 0){
  23. log.info("成功");
  24. return true;
  25. }else{
  26. log.info("失败");
  27. return false;
  28. }
  29. }
  30. }

Spring-Retry

简介

Spirng-Retry是Spring提供的一个重试框架,为spring程序提供声明式重试支持,主要针对可能抛出异常的调用操作,进行有策略的重试。可以通过代码方式和注解方式实现,主要由重试执行者和两个策略构成:

  • RetryTemplet:重试执行者,可以设置重试策略(设置重试上线、如何重试)和回退策略(立即重试还是等待一段时间后重试,默认立即重试,如果需要配置等待一段时间后重试则需要指定回退策略),通过Execute提交执行操作,只有在调用时抛出指定配置的异常,才会执行重试

  • 重试策略:
    策略方式NeverRetryPolicy只允许调用RetryCallback一次,不允许重试AlwaysRetryPolicy允许无限重试,直到成功,此方式逻辑不当会导致死循环SimpleRetryPolicy固定次数重试策略,默认重试最大次数为3次,RetryTemplate默认使用的策略TimeoutRetryPolicy超时时间重试策略,默认超时时间为1秒,在指定的超时时间内允许重试ExceptionClassifierRetryPolicy设置不同异常的重试策略,类似组合重试策略,区别在于这里只区分不同异常的重试CircuitBreakerRetryPolicy有熔断功能的重试策略,需设置3个参数openTimeout、resetTimeout和delegateCompositeRetryPolicy组合重试策略,有两种组合方式,乐观组合重试策略是指只要有一个策略允许即可以重试,悲观组合重试策略是指只要有一个策略不允许即可以重试,但不管哪种组合方式,组合中的每一个策略都会执行

  • 重试回退策略:
    回退策略方式NoBackOffPolicy无退避算法策略,每次重试时立即重试FixedBackOffPolicy固定时间的退避策略,需设置参数sleeper和backOffPeriod,sleeper指定等待策略,默认是Thread.sleep,即线程休眠,backOffPeriod指定休眠时间,默认1秒UniformRandomBackOffPolicy随机时间退避策略,需设置sleeper、minBackOffPeriod和maxBackOffPeriod,该策略在minBackOffPeriod,maxBackOffPeriod之间取一个随机休眠时间,minBackOffPeriod默认500毫秒,maxBackOffPeriod默认1500毫秒ExponentialBackOffPolicy指数退避策略,需设置参数sleeper、initialInterval、maxInterval和multiplier,initialInterval指定初始休眠时间,默认100毫秒,maxInterval指定最大休眠时间,默认30秒,multiplier指定乘数,即下一次休眠时间为当前休眠时间*multiplierExponentialRandomBackOffPolicy随机指数退避策略,引入随机乘数可以实现随机乘数回退

  • 此外,还需要配置重试时间间隔、最大重试次数以及可重试异常

实现

代码方式

RetryTemplate通过execute提交执行操作,需要准备RetryCallback和RecoveryCallback两个类实例,前者对应的就是重试回调逻辑实例,包装正常的功能操作,RecoveryCallback实现的是整个执行操作结束的恢复操作实例,只有在调用的时候抛出了异常,并且异常是在exceptionMap中配置的异常,才会执行重试操作,否则就调用到excute方法的第二个执行方法RecoveryCallback中

  1. package com.example.test.MessageRetry;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.junit.jupiter.api.Test;
  4. import org.springframework.remoting.RemoteAccessException;
  5. import org.springframework.retry.annotation.Backoff;
  6. import org.springframework.retry.annotation.Recover;
  7. import org.springframework.retry.annotation.Retryable;
  8. import org.springframework.retry.backoff.FixedBackOffPolicy;
  9. import org.springframework.retry.policy.SimpleRetryPolicy;
  10. import org.springframework.retry.support.RetryTemplate;
  11. import org.springframework.stereotype.Service;
  12. import java.util.HashMap;
  13. import java.util.Map;
  14. @Service
  15. @Slf4j
  16. public class SpringRetryService {
  17. /**
  18. * 重试时间间隔ms,默认1000ms
  19. */
  20. private long retryPeriodTime = 5000L;
  21. /**
  22. * 最大重试次数
  23. */
  24. private int maxRetryNum = 3;
  25. /**
  26. * 哪些结果和异常要重试,key表示异常类型,value表示是否需要重试
  27. */
  28. private Map<Class<? extends Throwable>,Boolean> retryMap = new HashMap<>();
  29. @Test
  30. public void test(){
  31. retryMap.put(IllegalArgumentException.class,true);
  32. retryMap.put(RemoteAccessException.class,true);
  33. //构建重试模板
  34. RetryTemplate retryTemplate = new RetryTemplate();
  35. //设置重试回退操作策略,主要设置重试时间间隔
  36. FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
  37. backOffPolicy.setBackOffPeriod(retryPeriodTime);
  38. //设置重试策略,主要设置重试次数
  39. SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(maxRetryNum,retryMap);
  40. retryTemplate.setRetryPolicy(retryPolicy);
  41. retryTemplate.setBackOffPolicy(backOffPolicy);
  42. Boolean execute = retryTemplate.execute(
  43. //RetryCallback
  44. retryContext -> {
  45. boolean b = RetryTask.retryTask("aaa");
  46. log.info("调用结果:{}",b);
  47. return b;
  48. },
  49. retryContext -> {
  50. //RecoveryCallback
  51. log.info("到达最多尝试次数");
  52. return false;
  53. }
  54. );
  55. log.info("执行结果:{}",execute);
  56. }
  57. }

在这里插入图片描述

注解方式

上面我们说到Spring-Retry是Spring提供的,那么,它就支持依赖整合

  1. <!--spring-retry-->
  2. <dependency>
  3. <groupId>org.springframework.retry</groupId>
  4. <artifactId>spring-retry</artifactId>
  5. <version>1.2.2.RELEASE</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.aspectj</groupId>
  9. <artifactId>aspectjweaver</artifactId>
  10. <version>1.9.1</version>
  11. </dependency>

然后,在启动类上添加开启注解

  1. //表示是否开启重试,属性proxyTargetClass,boolean类型,是否创建基于子类(CGLIB)的代理,而不是标准的基于接口的代理,默认false
  2. @EnableRetry
  1. package com.example.test.MessageRetry;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.apache.commons.lang3.RandomUtils;
  4. import org.springframework.remoting.RemoteAccessException;
  5. @Slf4j
  6. public class RetryTask {
  7. /**
  8. * 重试方法
  9. * @param param
  10. * @return
  11. */
  12. public static boolean retryTask(String param){
  13. log.info("请求参数:{}",param);
  14. int i = RandomUtils.nextInt(0,15);
  15. log.info("随机数:{}",i);
  16. if(i >-1){
  17. log.error("参数异常");
  18. throw new IllegalArgumentException("参数异常");
  19. // }else if(i > 10){
  20. // log.error("访问异常");
  21. // throw new RemoteAccessException("访问异常");
  22. // }else if(i % 2 == 0){
  23. // log.info("成功");
  24. // return true;
  25. }else{
  26. log.info("失败");
  27. return false;
  28. }
  29. }
  30. }
  1. /**
  2. * 重试调用方法
  3. * @param param
  4. * @return
  5. * @Retryable注解:
  6. *
  7. */
  8. @Retryable(value = {RemoteAccessException.class,IllegalArgumentException.class},maxAttempts = 3,backoff = @Backoff(delay = 5000L,multiplier = 2))
  9. public void call(String param){
  10. RetryTask.retryTask(param);
  11. }
  12. /**
  13. * 达到最大重试次数,或抛出了没有指定的异常
  14. * @param e
  15. * @param param
  16. * @return
  17. */
  18. @Recover
  19. public void recover(Exception e,String param){
  20. log.error("达到最大重试次数!!!!!");
  21. }
  1. @Test
  2. public void retry(){
  3. springRetryService.call("aaa");
  4. }
  • @Retryable注解说明
    属性说明value指定重试的异常类型,默认为空maxAttempts最大尝试次数,默认3次include和value一样,默认为空,当exclude也为空时,默认所有异常exclude指定不处理的异常Backoff重试策略

  • @Backoff注解说明:设定重试倍数,每次重试时间是上次的n倍
    属性说明delay重试之间的等待时间(以毫秒为单位),默认0maxDelay重试之间的最大等待时间(以毫秒为单位),默认0multiplier延迟的倍数,默认0.0delayExpression重试之间的等待时间表达式,默认空maxDelayExpression重试之间的最大等待时间表达式,默认空multiplierExpression指定延迟的倍数表达式,默认空random随机指定延迟时间,默认false

  • @Recover注解说明:当重试到达指定次数时,将要回调的方法

  • @Retryable和@Recover修饰的方法要在同一个类中,且被@Retryable和@Recover标记的方法不能有返回值,这样Recover方法才会生效。由于@Retryable注解是通过切面实现的,因此我们要避免@Retryable 注解的方法的调用方和被调用方处于同一个类中,因为这样会使@Retryable 注解失效。![在这里插入图片描述](https://img-blog.csdnimg.cn/22e61f2d194e4ac0aa061fa4ca7d483f.png 我们可以看到,Spring-Retry只能针对指定异常重试,不能根据执行结果返回值重试,整体使用也比较死板,下面,看下更加灵活的Guava-Retry。

Guava-Retry

简介

Guava-Retry是谷歌的Guava库的一个小扩展,允许为任意函数调用创建可配置的重试策略,我们可以通过构建重试实例RetryBuilder,来设置重试源、配置重试次数、重试超时时间、等待时间间隔等,实现优雅的重试机制。

  • 主要属性
    属性说明attemptTimeLimiter时间限制策略,单次任务执行时间限制,超时终止stopStrategy停止重试策略waitStrategy等待策略blockStrategy任务阻塞策略,即当前任务执行完,下次任务执行前做什么,仅有线程阻塞threadSleepStrategyretryException重试异常(重试策略)listeners自定义重试监听器,可用于记录日志等

  • 时间限制策略
    策略说明NoAttemptTimeLimit对代理方法不添加时间限制,默认FixedAttemptTimeLimit对代理方法的尝试添加固定时间限制

  • 重试策略(重试异常)
    策略说明retryIfException抛出 runtime 异常、checked 异常时都会重试,但是抛出 error 不会重试retryIfRuntimeException只会在抛 runtime 异常的时候才重试,checked 异常和error 都不重试retryIfExceptionOfType允许我们只在发生特定异常的时候才重试,比如NullPointerException 和 IllegalStateException 都属于 runtime 异常,也包括自定义的errorretryIfResult可以指定你的 Callable 方法在返回值的时候进行重试

  • 停止策略
    策略说明StopAfterDelayStrategy设定最长执行时间,无论任务执行几次,一旦超时,任务终止,返回RetryExceptionStopAfterAttemptStrategy设定最大尝试次数,一旦超过,返回重试异常NeverStopStrategy一直轮询直到获取期望结果

  • 等待策略
    策略说明ExceptionWaitStrategy异常时长等待,如果抛出的是指定异常,则从传入的方法中取得等待时间并返回;如果异常不匹配,则返回等待时间为0LCompositeWaitStrategy复合时长等待,在获取等待时间时会获取多种等待策略各自的等待时间,然后累加这些等待时间FibonacciWaitStrategy斐波那契等待策略ExponentialWaitStrategy指数等待时长,指数增长,若设置了最大时间,则停止,否则到Long.MAX_VALUEIncrementingWaitStrategy递增等待,提供一个初始时长和步长,随次数叠加RandomWaitStrategy随机等待时长,可以提供一个最大和最小时间,从范围内随机FixedWaitStrategy固定等待时长

    代码

  1. <!--guava-retryer-->
  2. <dependency>
  3. <groupId>com.github.rholder</groupId>
  4. <artifactId>guava-retrying</artifactId>
  5. <version>2.0.0</version>
  6. </dependency>
  1. package com.example.test.MessageRetry;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.apache.commons.lang3.RandomUtils;
  4. import org.springframework.remoting.RemoteAccessException;
  5. @Slf4j
  6. public class RetryTask {
  7. /**
  8. * 重试方法
  9. * @param param
  10. * @return
  11. */
  12. public static boolean retryTask(String param){
  13. log.info("请求参数:{}",param);
  14. int i = RandomUtils.nextInt(0,15);
  15. log.info("随机数:{}",i);
  16. if(i < 3){
  17. log.error("参数异常");
  18. throw new IllegalArgumentException("参数异常");
  19. }else if(i > 10){
  20. log.error("访问异常");
  21. throw new RemoteAccessException("访问异常");
  22. }else if(i % 2 == 0){
  23. log.info("成功");
  24. return true;
  25. }else{
  26. log.info("失败");
  27. return false;
  28. }
  29. }
  30. }
  1. package com.example.test.MessageRetry;
  2. import com.github.rholder.retry.*;
  3. import com.google.common.base.Predicates;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.springframework.remoting.RemoteAccessException;
  6. import org.springframework.stereotype.Service;
  7. import java.util.concurrent.TimeUnit;
  8. @Service
  9. @Slf4j
  10. public class GuavaRetryService {
  11. public void guavaRetry(){
  12. //构建重试实例RetryBuilder,可以设置重试源,可以配置重试次数、重试超时时间、等待时间间隔
  13. Retryer<Boolean>retryer = RetryerBuilder.<Boolean>newBuilder()
  14. //设置异常重试源
  15. .retryIfExceptionOfType(RemoteAccessException.class)
  16. .retryIfExceptionOfType(IllegalArgumentException.class)
  17. //设置根据结果重试 res->res==false Predicates.containsPattern("_error$")
  18. .retryIfResult(Predicates.equalTo(false))
  19. //设置等待时间
  20. .withWaitStrategy(WaitStrategies.fixedWait(3, TimeUnit.SECONDS))
  21. //设置最大重试次数
  22. .withStopStrategy(StopStrategies.stopAfterAttempt(3))
  23. //设置重试监听,可用作重试时的额外动作
  24. .withRetryListener(new RetryListener() {
  25. @Override
  26. public <V> void onRetry(Attempt<V> attempt) {
  27. log.error("第【{}】次调用失败",attempt.getAttemptNumber());
  28. }
  29. })
  30. //设置阻塞策略
  31. .withBlockStrategy(BlockStrategies.threadSleepStrategy())
  32. //设置时间限制
  33. .withAttemptTimeLimiter(AttemptTimeLimiters.noTimeLimit())
  34. .build();
  35. try{
  36. retryer.call(()->RetryTask.retryTask("aaa"));
  37. }catch (Exception e){
  38. e.printStackTrace();
  39. }
  40. }
  41. }

在这里插入图片描述

可以看到,我们设置了重试三次,超过这个限制没有执行成功,抛出了重试异常,而且也可以根据我们的返回结果来判断。

总结

Spring-Retry和Guava-Retry都是线程安全的重试框架,能够保证并发业务下重试逻辑的正确性。两者都很好的将正常方法和重试方法进行了解耦,可以设置超时时间、重试次数、间隔时间、监听结果等,相比来说,Guava-Retry比Spring-Retry更加灵活,并且可以通过返回值来进行重试,两者都是非常好的重试框架,具体的选用看相关的业务场景即可。

标签: spring guava java

本文转载自: https://blog.csdn.net/qq_36933421/article/details/132162078
版权归原作者 雅俗共赏zyyyyyy 所有, 如有侵权,请联系我们删除。

“重试框架入门:Spring-Retry&Guava-Retry”的评论:

还没有评论