• 重试组件 Spring Retry


    spring-retry是spring社区的一个成员,它提供了一种对失败操作进行自动重试的能力,可以作为某些瞬时错误(例如短暂的网络抖动)的解决方案。

    作为spring生态的一部分,spring-retry自然地支持声明式(Declarative)方式使用。此外,它也支持命令式(Impertive)方式在代码里直接调用。

    1. 项目集成

    引入依赖:

    <dependency>
        <groupId>org.springframework.retrygroupId>
        <artifactId>spring-retryartifactId>
        <version>1.2.2.RELEASEversion>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    它的版本交给spring boot管理,以获得与spring良好的兼容性。

    项目当前使用的是spring-boot版本为1.5.13.RELEASE,它管理的spring-retry版本是1.2.2.RELEASE。

    2. 声明式使用方式

    1. 在spring boot启动类上增加@EnableRetry注解:

      @EnableRetry(proxyTargetClass = true)
      @SpringBootApplication
      public class TestApplication {
          public static void main(String[] args) {
              SpringApplication.run(TestApplication.class, args);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      该注解的proxyTargetClass属性默认为false,表示使用JDK的动态代理。如果设置为true,则表示使用CGLIB作为动态代理。

    2. 在需要重试的方法上,增加@Retryable注解:

      @Service
      @Slf4j
      public class RetryAnnotationTest {
      
          @Retryable(value = {RemoteAccessException.class}, maxAttempts = 5, backoff = @Backoff(delay = 1000L), recover = "recoverCall")
          public boolean call(String param){
              System.out.println(new Date());
              return RetryTask.retryTask(param);
          }
      
          @Recover
          public boolean recoverCall(Exception e,String param) {
              log.error("达到最大重试次数,或抛出了一个没有指定进行重试的异常:", e);
              return false;
       }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16

      如上示例中,当call方法抛出RemoteAccessException异常时,spring retry会重新调用call方法,重试次数为5次,两次重试之间间隔为1s。

      如果超过最大重试次数仍未成功,或者抛出非RemoteAccessException异常,则调用recoverCall方法。

      注:@Retryable注解也可以作用在类上,作用在类上之后,spring retry会对类的全部方法进行重试。

    3. 命令式使用方式

    1. 配置RetryTemplate:

      @Configuration
      public class SpringRetryConfig {
         
          @Bean("retryTemplateFixed")
          public RetryTemplate retryTemplateFixed() {
              // 1.重试策略
              // 触发条件
              Map<Class<? extends Throwable>, Boolean> exceptionMap = new HashMap<>();
              exceptionMap.put(RemoteAccessException.class, true);
              
              // 重试次数设置为3次
              int maxAttempts = 3;
              SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(maxAttempts, exceptionMap);
      
              // 2.重试间隔设置为1秒
              FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
              backOffPolicy.setBackOffPeriod(1000);
      
              // 3.构造RetryTemplate
              RetryTemplate retryTemplate = new RetryTemplate();
              retryTemplate.setRetryPolicy(retryPolicy);
              retryTemplate.setBackOffPolicy(backOffPolicy);
              return retryTemplate;
          }
          
          @Bean("retryTemplate")
          public RetryTemplate retryTemplate() {
              // 定义简易重试策略,最大重试次数为3次,重试间隔为3s
              return RetryTemplate.builder()
                      .maxAttempts(3)
                      .fixedBackoff(3000)
                      .retryOn(RuntimeException.class)
                      .build();
          }
          
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36

      注:可以配置多个RetryTemplate,用以适应不同重试场景。

      spring retry支持的重试策略和退避策略如下:

      @Bean("retryTemplateDemo")
      public RetryTemplate retryTemplateDemo() {
          // 1.重试策略
          // 不重试
          NeverRetryPolicy neverRetryPolicy = new NeverRetryPolicy();
      
          // 无限重试
          AlwaysRetryPolicy alwaysRetryPolicy = new AlwaysRetryPolicy();
      
          // 设置不同异常的重试策略,类似组合重试策略,区别在于这里只区分不同异常的重试
          ExceptionClassifierRetryPolicy exceptionClassifierRetryPolicy = new ExceptionClassifierRetryPolicy();
          final Map<Class<? extends Throwable>, RetryPolicy> policyMap = new HashMap<>(3);
          policyMap.put(IOException.class, alwaysRetryPolicy);
          policyMap.put(InterruptedIOException.class, neverRetryPolicy);
          policyMap.put(UnknownHostException.class, neverRetryPolicy);
          exceptionClassifierRetryPolicy.setPolicyMap(policyMap);
      
          // 固定次数重试,默认最大重试次数为5次,RetryTemplate默认重试策略
          SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
          simpleRetryPolicy.setMaxAttempts(5);
      
          // 超时时间重试,默认超时时间为1秒,在指定的超时时间内重试
          TimeoutRetryPolicy timeoutRetryPolicy = new TimeoutRetryPolicy();
          timeoutRetryPolicy.setTimeout(3000);
      
          /*
           * 组合重试策略,有两种组合方式:
           *  1.悲观默认重试,有不重试的策略则不重试。
           *  2.乐观默认不重试,有需要重试的策略则重试。
           */
          CompositeRetryPolicy compositeRetryPolicy = new CompositeRetryPolicy();
          compositeRetryPolicy.setOptimistic(true);
          compositeRetryPolicy.setPolicies(new RetryPolicy[]{simpleRetryPolicy, timeoutRetryPolicy});
      
          // 有熔断功能的重试
          CircuitBreakerRetryPolicy circuitBreakerRetryPolicy = new CircuitBreakerRetryPolicy(compositeRetryPolicy);
          // 5s内失败10次,则开启熔断
          circuitBreakerRetryPolicy.setOpenTimeout(5000);
          // 10s之后熔断恢复
          circuitBreakerRetryPolicy.setResetTimeout(10000);
      
          // 2.退避策略(上一次执行失败之后,间隔多久进行下一次重试)
          // 立即重试
          NoBackOffPolicy noBackOffPolicy = new NoBackOffPolicy();
      
          // 固定时间后重试,默认1s
          FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
          fixedBackOffPolicy.setBackOffPeriod(1000);
      
          // 随机时间后重试(如下:从500ms到1500ms内取一个随机时间后进行重试)
          UniformRandomBackOffPolicy uniformRandomBackOffPolicy = new UniformRandomBackOffPolicy();
          uniformRandomBackOffPolicy.setMinBackOffPeriod(500);
          uniformRandomBackOffPolicy.setMaxBackOffPeriod(1500);
      
          // 指数退避策略(如下:初始休眠时间100ms,最大休眠时间30s,下一次休眠时间为当前休眠时间*2)
          ExponentialBackOffPolicy exponentialBackOffPolicy = new ExponentialBackOffPolicy();
          exponentialBackOffPolicy.setInitialInterval(100);
          exponentialBackOffPolicy.setMaxInterval(30000);
          exponentialBackOffPolicy.setMultiplier(2);
      
          // 随机指数退避策略
          ExponentialRandomBackOffPolicy exponentialRandomBackOffPolicy = new ExponentialRandomBackOffPolicy();
          exponentialRandomBackOffPolicy.setInitialInterval(100);
          exponentialRandomBackOffPolicy.setMaxInterval(30000);
          exponentialRandomBackOffPolicy.setMultiplier(2);
      
          // 3.return
          RetryTemplate retryTemplate = new RetryTemplate();
          retryTemplate.setRetryPolicy(circuitBreakerRetryPolicy);
          return retryTemplate;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
    2. 在代码中调用

      @Slf4j
      @RunWith(SpringRunner.class)
      @SpringBootTest
      public class RetryTest {
      
          // 注入RetryTemplate
          @Resource
          private RetryTemplate retryTemplateFixed;
      
          @Test
          public void test() {
              // 执行
              Boolean execute = retryTemplateFixed.execute(
                      // 重试回调
                      retryContext -> {
                          System.out.println(new Date());
                          boolean b = RetryTask.retryTask("abc");
                          log.info("调用的结果:{}", b);
                          return b;
                      },
                      // 恢复回调(达到最大重试次数,或者抛出不满足重试条件的异常)
                      retryContext -> {
                          log.info("已达到最大重试次数或抛出了不重试的异常~~~");
                          return false;
                      }
              );
      
              log.info("执行结果:{}",execute);
      
          }
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
    3. 监听重试过程

      通过实现RetryListener接口,重写open、close、onError这三个方法,既可以完成对重试过程的追踪,也可以添加额外的处理逻辑。

      @Slf4j
      @Component
      public class RetryListenerTemplate implements RetryListener {
          // 进入重试前调用
          @Override
          public <T, E extends Throwable> boolean open(RetryContext retryContext, RetryCallback<T, E> retryCallback) {
              log.info("--------------------------进入重试方法--------------------------");
          return true;
          }
      
          // 重试结束后调用
          @Override
          public <T, E extends Throwable> void close(RetryContext retryContext, RetryCallback<T, E> retryCallback, Throwable throwable) {
          log.info("--------------------------重试方法结束--------------------------");
          }
      
          // 捕获到异常时调用
          @Override
          public <T, E extends Throwable> void onError(RetryContext retryContext, RetryCallback<T, E> retryCallback, Throwable throwable) {
              log.info("--------------------------第" + retryContext.getRetryCount() + "次重试--------------------------");
              log.error(throwable.getMessage(), throwable);
      }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23

      或者,通过继承RetryListenerSupport,也可以从open、close、onError这三个方法中,选择性的重写。

      public class RetryListener4Open extends RetryListenerSupport {
          @Override
          public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
              return super.open(context, callback);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      在实例化RetryTemplate时,配置上该RetryListener实例即可。

      retryTemplate.setListeners(new RetryListener[] {retryListenerTemplate});
      
      • 1

      注:

      1. V2.0版本以后,新增一个doOnSuccess方法,可以在调用成功之后,根据返回的结果值,来决定是否要进行重试。
      2. 每个RetryTemplate可以注册多个监听器,其中onOpen、onClose方法按照注册顺序执行,onError按照注册顺序的相反顺序执行。

    参考资料:

    1. Spring-Retry 和 Guava-Retry
    2. Spring Retry Github地址
  • 相关阅读:
    static关键字
    Node.js 初学者教程
    excel系列【统计一列中的不重复项】
    三、飞行和射击
    DTU配置工具-F2x16工具
    react项目导出数据doc格式及其他格式方法
    使用 Echarts 插件实现柱状图功能
    论文笔记:Code Llama: Open Foundation Models for Code
    .NET高级面试指南专题十四【 观察者模式介绍,最常用的设计模式之一】
    面试面经|Java开发面试JVM面试题
  • 原文地址:https://blog.csdn.net/weixin_34850743/article/details/126820853