项目中为了保证处理更健壮,容错性更高,更不容易失败,使用自动重试的失败的操作,可提高后续操作的可用性,保证容错性。Spring实提供了自动重试机制,功能简单实用。当错误引起失败是暂时性的情况下,非常适用。比如操作中暂时的网络故障,或者数据库操作由暂时锁引起的异常等。
在微服务中通常都提供了重试与超时配置,比如SpringCloud的Feign组件。在SpringBoot的单应用项目中,我们则可以使用Spring提供的Spring Retry实现自动重试功能。

Retry框架介绍

Retry重试框架,支持AOP切入的方式使用,支持注解;重试次数、重试延迟、重试触发条件、重试的回调方法等功能来实现重试机制。
项目中我们可以通过两种不同的方式使用Spring Retry重试功能,一种是@Retryable注解的方式,另一种是RetryTemplate方式。

springboot整合Retry实现Retryable注解重试

pom文件加入依赖

  1. <dependency>
  2. <groupId>org.springframework.retry</groupId>
  3. <artifactId>spring-retry</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.aspectj</groupId>
  7. <artifactId>aspectjweaver</artifactId>
  8. </dependency>

容器启动类上添加注解@EnableRetry

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

  1. @EnableRetry
  2. @SpringBootApplication
  3. @MapperScan("com.qingfeng.*.mapper")
  4. @EnableMyRedis
  5. @EnableMyProtect
  6. @ServletComponentScan
  7. //@ServletComponentScan("com.qingfeng.framework.servlet")
  8. public class QingfengApplication {
  9. @Value("${spring.application.name}")
  10. private String application;
  11. public static void main(String[] args) {
  12. SpringApplication.run(QingfengApplication.class, args);
  13. }
  14. @Bean
  15. MeterRegistryCustomizer<MeterRegistry> configurer() {
  16. return (registry) -> registry.config().commonTags("application", application);
  17. }
  18. }

image.png

创建测试Service

  1. @Service
  2. @Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
  3. public class TTestServiceImpl implements ITestService {
  4. private final static int TOTAL_NUM = 100000;
  5. @Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 5000L, multiplier = 2))
  6. public int getRetryNum(int num) throws Exception {
  7. System.out.println("getRemainingAmount======" + DateTimeUtil.getDateTimeStr());
  8. if (num <= 0) {
  9. throw new Exception("数量不对");
  10. }
  11. System.out.println("getRemainingAmount======执行结束");
  12. return TOTAL_NUM - num;
  13. }
  14. @Recover
  15. public int recover(Exception e) {
  16. // 回调方法,业务逻辑处理
  17. return -1;
  18. }
  19. }

@Retryable:标记当前方法使用重试机制
value:触发重试机制的条件,当遇到Exception时,会重试
maxAttempts :设置最大重试次数,默认为3次
delay:重试延迟时间,单位毫秒,即距离上一次重试方法的间隔
multiplier:delay重试延迟时间的间隔倍数,即第一次为5秒,第二次为5乘以2为10秒,依此类推

创建测试Controller

  1. @GetMapping("/retryTest")
  2. public String retryTest(@RequestParam int num) throws Exception {
  3. int retrynum = testService.getRetryNum(num);
  4. log.info("剩余数量===" + retrynum);
  5. return "success";
  6. }

运行测试

在浏览器中输入:http://localhost:8090/common/test/retryTest?num=0
image.png
image.png
可以看到,第一次用时5S,第二次用时10S,第三次用时15S;
最后会抛出异常,可以在上层代码进行try-catch处理相关业务逻辑;
也可以写回调方法处理,在TestServiceImpl类中添加如下代码,@Recover:当重试方法发生异常时,会执行该回调方法

  1. @Recover
  2. public int recover(Exception e) {
  3. // 回调方法,业务逻辑处理
  4. return -1;
  5. }

image.png

springboot整合RetryTemplate实现重试

配置RetryTemplate

首先配置RetryTemplate并注册为Java Bean交有Spring管理

  1. @EnableRetry
  2. @SpringBootApplication
  3. @MapperScan("com.qingfeng.*.mapper")
  4. @EnableMyRedis
  5. @EnableMyProtect
  6. @ServletComponentScan
  7. //@ServletComponentScan("com.qingfeng.framework.servlet")
  8. public class QingfengApplication {
  9. @Value("${spring.application.name}")
  10. private String application;
  11. public static void main(String[] args) {
  12. SpringApplication.run(QingfengApplication.class, args);
  13. }
  14. @Bean
  15. MeterRegistryCustomizer<MeterRegistry> configurer() {
  16. return (registry) -> registry.config().commonTags("application", application);
  17. }
  18. @Bean
  19. public RetryTemplate retryTemplate() {
  20. /**
  21. * The RetryPolicy determines when an operation should be retried.
  22. * 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.
  23. * Finally, a FixedBackOffPolicy pauses for a fixed period of time before continuing.
  24. */
  25. RetryTemplate retryTemplate = new RetryTemplate();
  26. FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
  27. fixedBackOffPolicy.setBackOffPeriod(2000l);
  28. retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
  29. SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
  30. retryPolicy.setMaxAttempts(2);
  31. retryTemplate.setRetryPolicy(retryPolicy);
  32. retryTemplate.registerListener(new AppRetryListenerSupport());
  33. return retryTemplate;
  34. }
  35. }

1、setBackOffPeriod设置重试间隔2秒。
2、setMaxAttempts设置最大重试次数2次。
3、registerListener注册retryTemplate监听器。

创建监听器AppRetryListenerSupport

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

  1. package com.qingfeng.framework.retry;
  2. import org.slf4j.Logger;
  3. import org.slf4j.LoggerFactory;
  4. import org.springframework.retry.RetryCallback;
  5. import org.springframework.retry.RetryContext;
  6. import org.springframework.retry.listener.RetryListenerSupport;
  7. /**
  8. * @author Administrator
  9. * @version 1.0.0
  10. * @ProjectName qingfeng
  11. * @Description TODO
  12. * @createTime 2022年04月28日 20:54:00
  13. */
  14. public class AppRetryListenerSupport extends RetryListenerSupport {
  15. private static final Logger LOGGER = LoggerFactory.getLogger(AppRetryListenerSupport.class);
  16. @Override
  17. public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
  18. LOGGER.info("The retry is closed.");
  19. System.out.println("The retry is closed.");
  20. super.close(context, callback, throwable);
  21. }
  22. @Override
  23. public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
  24. LOGGER.info("The retry is on error.");
  25. System.out.println("The retry is on error.");
  26. // 重试的时候如果需要处理一些其他逻辑,可以在该方法内增加
  27. super.onError(context, callback, throwable);
  28. }
  29. @Override
  30. public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
  31. LOGGER.info("The retry is open.");
  32. System.out.println("The retry is open.");
  33. return super.open(context, callback);
  34. }
  35. }

测试retryTemplate.execute()

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

  1. @Autowired
  2. private RetryTemplate retryTemplate;
  3. @GetMapping("/retryTemplateTest")
  4. public Object retryTemplateTest(@RequestParam int num) throws Exception {
  5. try {
  6. Object result = retryTemplate.execute(arg -> testService.getRetryTemplateNum(num));
  7. return result;
  8. } catch (Exception e) {
  9. e.printStackTrace();
  10. }
  11. return null;
  12. }

重启项目,然后在浏览器中输入:
http://localhost:8090/common/test/retryTemplateTest?num=0
image.png

Retry问题分析

由于retry用到了aspect增强,所有会有aspect的坑,就是方法内部调用,会使aspect增强失效,那么retry当然也会失效。

  1. public class demo {
  2. public void A() {
  3. B();
  4. }
  5. //这里B不会执行
  6. @Retryable(Exception.class)
  7. public void B() {
  8. throw new RuntimeException("retry...");
  9. }
  10. }

重试机制,不能在接口实现类里面写。所以要做重试,必须单独写个service。
maxAttemps参数解释的是说重试次数,测试的时候发现这个=1时,方法一共只执行了一次,=3时,一共只执行3次。