项目中为了保证处理更健壮,容错性更高,更不容易失败,使用自动重试的失败的操作,可提高后续操作的可用性,保证容错性。Spring实提供了自动重试机制,功能简单实用。当错误引起失败是暂时性的情况下,非常适用。比如操作中暂时的网络故障,或者数据库操作由暂时锁引起的异常等。

在微服务中通常都提供了重试与超时配置,比如SpringCloud的Feign组件。在SpringBoot的单应用项目中,我们则可以使用Spring提供的Spring Retry实现自动重试功能。

引入Spring Retry依赖

Spring Retry是从Spring Batch独立出来的一个功能,所以在SpringBoot项目中我们需要在pom.xml文件中引入其依赖:

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

启用Spring Retry重试机制

在任一的@Configuration配置类上添加@EnableRetry注解开启Spring Retry的重试功能。

@Configuration
// 开启Retry重试机制
@EnableRetry
public class AppConfig {

}

使用Spring Retry重试功能

项目中我们可以通过两种不同的方式使用Spring Retry重试功能,一种是@Retryable注解的方式,另一种是RetryTemplate方式。

@Retryable注解

通过@RetryAble注解完成自动重试的配置,配置参数详情参考官方文档。这样调用retryAnnotationService方法即完成了自动重试的功能。@Recover注解即多次重试后还是失败则调用该注解的方法,需要位于与@RetryAble注解的方法的同一个类中。

@Service 
public class DemoService {  
  
    @Retryable(value = Exception.class, maxAttempts = 2,  
            backoff= @Backoff(delay = 100))  
    public void retryAnnotationService() throws Exception{  
        // 调用外部接口,或者连接数据等业务逻辑
        System.out.println("service method...");  
        throw new Exception("Test exception");  
    }  
  
    public Object retryTemplateService() {
        // 调用外部接口,或者连接数据等业务逻辑
    }

    @Recover  
    public void recover(Exceptione){  
        System.out.println("recover: " + e.getMessage());  
    }  
  
}

RetryTemplate

首先配置RetryTemplate并注册为Java Bean交有Spring管理。RetryTemplate的详细配置以及说明可以参考官方文档。

@Configuration
// 开启Retry重试机制
@EnableRetry
public class AppConfig {

    @Value("${app.retry.max-attempts:2}")
    private Integer maxAttempts;

    @Value("${app.retry.back-off-period:1000}")
    private Long backOffPeriod;


    @Bean
    public RetryTemplate retryTemplate() {
        /**
         * The RetryPolicy determines when an operation should be retried.
         * A SimpleRetryPolicy is used to retry a fixed number of times. On the other hand, the BackOffPolicy is used to control backoff between retry attempts.
         * Finally, a FixedBackOffPolicy pauses for a fixed period of time before continuing.
         */
        RetryTemplate retryTemplate = new RetryTemplate();

        FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
        fixedBackOffPolicy.setBackOffPeriod(backOffPeriod);
        retryTemplate.setBackOffPolicy(fixedBackOffPolicy);

        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
        retryPolicy.setMaxAttempts(maxAttempts);
        retryTemplate.setRetryPolicy(retryPolicy);

        retryTemplate.registerListener(new AppRetryListenerSupport());

        return retryTemplate;
    }
}

实现监听器AppRetryListenerSupport,当重试的时候提供不同触发事件的回调方法,在回调中可以针对不同的触发事件进行处理。

public class AppRetryListenerSupport extends RetryListenerSupport {

    private static final Logger LOGGER = LoggerFactory.getLogger(AppRetryListenerSupport.class);


    @Override
    public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
        LOGGER.info("The retry is closed.");
        super.close(context, callback, throwable);
    }

    @Override
    public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
        LOGGER.info("The retry is on error.");

        // 重试的时候如果需要处理一些其他逻辑,可以在该方法内增加

        super.onError(context, callback, throwable);
    }

    @Override
    public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
        LOGGER.info("The retry is open.");

        return super.open(context, callback);
    }
}

 使用retryTemplate.execute() 调用需要需要自动重试的方法。

@RestController
@RequestMapping("demo")
public class DemoController {

    private static final Logger LOGGER = LoggerFactory.getLogger(DemoController.class);

    @Autowired
    private DemoService demoService;

    @Autowired
    private RetryTemplate retryTemplate;

    @GetMapping
    public Object list() {
        try {

            Object result = retryTemplate.execute(arg -> demoService.retryTemplateService());

            return result;
        } catch (Exception e) {
            LOGGER.error("异常详情: {}", e);
            return null;
        }


    }
}

总结

SpringBoot项目通过使用Spring Retry可以快速的使用自动重试的功能,当项目中调用数据库或者外部接口要保证可用性时,对于操作中暂时性的异常可以通过重试策略保证可用性。本文针对使用层面做了一些总结,未深入研究源码。希望其它网友使用Spring Retry重试功能的时候,能够通过此文获得一些帮助和参考。

参考

https://docs.spring.io/spring-batch/docs/current/reference/html/retry.html

https://www.baeldung.com/spring-retry

https://springboot.io/t/topic/2735