简要

Spring实现了一套重试机制,功能简单实用。Spring Retry是从Spring Batch独立出来的一个功能,已经广泛应用于Spring Batch,Spring Integration, Spring for Apache Hadoop等Spring项目。

本文将讲述如何使用Spring Retry及其实现原理。

1.为什么需要重试?

在调用一些第三方接口时候可能会因为网络或者服务方异常导致请求失败,这个时候可以通过重试解决这种问题。

以下面的简单的例子来了解 Retry的功能,下面有个doTask函数,执行该函数的时候如果出现异常则需要重试任务

  • 1.CountDownLatch 用于在主线程用于等待线程池中的任务完成
  • 2.AtomicInteger 类型用于计算重试次数
  • 3.ScheduledExecutorService用于定时执行需要重试的任务,如果没有异常则第一次执行完任务则会关闭线程池
  • 4.doTask函数中通过 1/0故意造成异常
    public static boolean doTask() {
        try {
            System.out.println(1/0);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 通过ScheduledExecutor定时器实现低配版本的重试机制
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        AtomicInteger count = new AtomicInteger(0);
        //设置重试的次数
        int retryNumber = 3;
        //创建单线程的定时任务处理器
        ScheduledExecutorService scheduledThreadPool = Executors.newSingleThreadScheduledExecutor();
        //(参数1=执行内容,参数2=初始延迟时间,参数3=任务间隔时间,参数4=时间单位)
        scheduledThreadPool.scheduleAtFixedRate(() -> {
            boolean flag = doTask();
            //业务是否处理成功,成功则关闭线程池
            if (flag || count.get() == retryNumber) {
                //执行成功或者已达到执行次数则关闭线程池
                scheduledThreadPool.shutdownNow();
                countDownLatch.countDown();;
            }else{
                log.info("第{}次重试任务", count.get()+1);
                count.getAndIncrement();
            }
        }, 0, 1, TimeUnit.SECONDS);
        //等待线程池中的任务完成
        countDownLatch.await();
    }

【SpringBoot框架篇】34.使用Spring Retry完成任务的重试-LMLPHP
把doTask函数中的导致异常的代码注释再运行可以看到控制台没有打印重试的信息

从上面代码可以看出写一个任务重试的工具不难,感兴趣的同学可以通过AOP代理的方式自己实现基于注解的重试功能,Spring官方的Retry模块里有通过Aop加注解的方式实现重试功能,Aop玩腻了,我就不造轮子了。。

2.添加maven依赖

由于retry依赖中没有包含aspectj相关依赖,所以需要单独引用aspectj

    <!-- https://mvnrepository.com/artifact/org.springframework.retry/spring-retry -->
        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
            <version>1.3.4</version>
        </dependency>

        <!--aop切面-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
        
      <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

3.使用@Retryable注解实现重试

3.1.需要在springboot启动添加@EnableRetry注解开启对Retry的支持

@EnableRetry
@SpringBootApplication
public class RetryApplication {
}

3.2.定义需要重试任务的异常类型

public class CustomRecoveryException extends Exception{
    public CustomRecoveryException(String error){
        super(error);
    }
}

3.3.在需要任务重试的函数上面添加注解

  • value属性表示在哪些异常的情况下才会去重试,多个类型用,隔开。
  • maxAttempts属性设置执行次数,默认值为3则表示异常后只会重试两次
  • backoff属性设置下次重试的延迟时间,默认值为1000ms(1秒)。
@Slf4j
@Service
public class RetryServer  {
    @Retryable(value = {CustomRecoveryException.class, IOException.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000))
    public void retryTest() throws CustomRecoveryException {
        log.info("retryTest,当前时间:{}",LocalDateTime.now());
        throw new CustomRecoveryException("test retry error");
    }

3.4.通过@Recover定义降级处理的函数
返回值需要和重试的任务一致,要不然会抛出异常。

    @Recover
    public void fallback(Throwable throwable) {
        // 降级处理逻辑
        log.error("fallback,Error msg:{}",throwable.getMessage());
        return "fallback";
    }
   } 

3.5.使用junit进行测试

@SpringBootTest
class RetryApplicationTests {

    @Autowired
    private RetryServer retryServer;

    @Test
    void contextLoads() throws CustomRecoveryException {
        retryServer.retryTest();
    }
}    

可以看到控制台只打印了三次日志,从这能确认任务共执行了三次,只重试了两次。
【SpringBoot框架篇】34.使用Spring Retry完成任务的重试-LMLPHP

4.基于RetryTemplate模板实现重试

4.1.配置RetryTemplate重试的策略

@EnableRetry
@SpringBootApplication
public class RetryApplication {
    public static void main(String[] args) {
        SpringApplication.run(RetryApplication.class, args);
    }

    @Bean
    public RetryTemplate retryTemplate() {
        final SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
        //设置最多执行次数, 包含第一次执行,下面配置成3,则第一次执行出现异常后最多会再重试2次
        simpleRetryPolicy.setMaxAttempts(3);
        final FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
        //设置重试间隔时间  单位 ms
        fixedBackOffPolicy.setBackOffPeriod(1000L);
        return RetryTemplate.builder()
                .customPolicy(simpleRetryPolicy)
                .customBackoff(fixedBackOffPolicy)
                .build();
    }
}

4.2.添加任务重试失败之后的降级处理回调函数

@Slf4j
@Component
public class CustomRecoveryCallback implements RecoveryCallback<String> {
    @Override
    public String recover(RetryContext retryContext) {
        log.error("fallback,retryCount:{},error msg:{}",retryContext.getRetryCount(),retryContext.getLastThrowable().getMessage());
        return "fallback";
    }
}

4.3.通过retryTemplate.execute执行需要重试的任务

@Slf4j
@Service
public class RetryServer  {

    @Autowired
    private RetryTemplate retryTemplate;

    @Autowired
    private CustomRecoveryCallback customRecoveryCallback;

    public void retryTemplateTest() {
        //第一个参数是只需要执行的方法,第二个参数是降级处理方法
        retryTemplate.execute(f->function(),customRecoveryCallback);
    }
    
  	/**
     * 具体的执行任务
     */
    public String function(){
        log.info("retryTemplateTest,当前时间:{}",LocalDateTime.now());
        throw new RuntimeException("test retry error");
    }

}

4.4.使用junit进行测试

@SpringBootTest
class RetryApplicationTests {

    @Autowired
    private RetryServer retryServer;

    @Test
    void contextLoads() {
        retryServer.retryTemplateTest();
    }
    
   }

可以看到测试得到的结果和注解的方式是一样的,都只执行了三次任务。
【SpringBoot框架篇】34.使用Spring Retry完成任务的重试-LMLPHP

01-04 05:18