项目中为了保证处理更健壮,容错性更高,更不容易失败,使用自动重试的失败的操作,可提高后续操作的可用性,保证容错性。Spring实提供了自动重试机制,功能简单实用。当错误引起失败是暂时性的情况下,非常适用。比如操作中暂时的网络故障,或者数据库操作由暂时锁引起的异常等。
在微服务中通常都提供了重试与超时配置,比如SpringCloud的Feign组件。在SpringBoot的单应用项目中,我们则可以使用Spring提供的Spring Retry实现自动重试功能。
Retry框架介绍
Retry重试框架,支持AOP切入的方式使用,支持注解;重试次数、重试延迟、重试触发条件、重试的回调方法等功能来实现重试机制。
项目中我们可以通过两种不同的方式使用Spring Retry重试功能,一种是@Retryable注解的方式,另一种是RetryTemplate方式。
springboot整合Retry实现Retryable注解重试
pom文件加入依赖
<dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</artifactId></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId></dependency>
容器启动类上添加注解@EnableRetry
也可以在任一的@Configuration配置类上添加@EnableRetry注解开启Spring Retry的重试功能。
@EnableRetry@SpringBootApplication@MapperScan("com.qingfeng.*.mapper")@EnableMyRedis@EnableMyProtect@ServletComponentScan//@ServletComponentScan("com.qingfeng.framework.servlet")public class QingfengApplication {@Value("${spring.application.name}")private String application;public static void main(String[] args) {SpringApplication.run(QingfengApplication.class, args);}@BeanMeterRegistryCustomizer<MeterRegistry> configurer() {return (registry) -> registry.config().commonTags("application", application);}}

创建测试Service
@Service@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)public class TTestServiceImpl implements ITestService {private final static int TOTAL_NUM = 100000;@Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 5000L, multiplier = 2))public int getRetryNum(int num) throws Exception {System.out.println("getRemainingAmount======" + DateTimeUtil.getDateTimeStr());if (num <= 0) {throw new Exception("数量不对");}System.out.println("getRemainingAmount======执行结束");return TOTAL_NUM - num;}@Recoverpublic int recover(Exception e) {// 回调方法,业务逻辑处理return -1;}}
@Retryable:标记当前方法使用重试机制
value:触发重试机制的条件,当遇到Exception时,会重试
maxAttempts :设置最大重试次数,默认为3次
delay:重试延迟时间,单位毫秒,即距离上一次重试方法的间隔
multiplier:delay重试延迟时间的间隔倍数,即第一次为5秒,第二次为5乘以2为10秒,依此类推
创建测试Controller
@GetMapping("/retryTest")public String retryTest(@RequestParam int num) throws Exception {int retrynum = testService.getRetryNum(num);log.info("剩余数量===" + retrynum);return "success";}
运行测试
在浏览器中输入:http://localhost:8090/common/test/retryTest?num=0

可以看到,第一次用时5S,第二次用时10S,第三次用时15S;
最后会抛出异常,可以在上层代码进行try-catch处理相关业务逻辑;
也可以写回调方法处理,在TestServiceImpl类中添加如下代码,@Recover:当重试方法发生异常时,会执行该回调方法
@Recoverpublic int recover(Exception e) {// 回调方法,业务逻辑处理return -1;}
springboot整合RetryTemplate实现重试
配置RetryTemplate
首先配置RetryTemplate并注册为Java Bean交有Spring管理
@EnableRetry@SpringBootApplication@MapperScan("com.qingfeng.*.mapper")@EnableMyRedis@EnableMyProtect@ServletComponentScan//@ServletComponentScan("com.qingfeng.framework.servlet")public class QingfengApplication {@Value("${spring.application.name}")private String application;public static void main(String[] args) {SpringApplication.run(QingfengApplication.class, args);}@BeanMeterRegistryCustomizer<MeterRegistry> configurer() {return (registry) -> registry.config().commonTags("application", application);}@Beanpublic 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(2000l);retryTemplate.setBackOffPolicy(fixedBackOffPolicy);SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();retryPolicy.setMaxAttempts(2);retryTemplate.setRetryPolicy(retryPolicy);retryTemplate.registerListener(new AppRetryListenerSupport());return retryTemplate;}}
1、setBackOffPeriod设置重试间隔2秒。
2、setMaxAttempts设置最大重试次数2次。
3、registerListener注册retryTemplate监听器。
创建监听器AppRetryListenerSupport
实现监听器AppRetryListenerSupport,当重试的时候提供不同触发事件的回调方法,在回调中可以针对不同的触发事件进行处理。
package com.qingfeng.framework.retry;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.retry.RetryCallback;import org.springframework.retry.RetryContext;import org.springframework.retry.listener.RetryListenerSupport;/*** @author Administrator* @version 1.0.0* @ProjectName qingfeng* @Description TODO* @createTime 2022年04月28日 20:54:00*/public class AppRetryListenerSupport extends RetryListenerSupport {private static final Logger LOGGER = LoggerFactory.getLogger(AppRetryListenerSupport.class);@Overridepublic <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {LOGGER.info("The retry is closed.");System.out.println("The retry is closed.");super.close(context, callback, throwable);}@Overridepublic <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {LOGGER.info("The retry is on error.");System.out.println("The retry is on error.");// 重试的时候如果需要处理一些其他逻辑,可以在该方法内增加super.onError(context, callback, throwable);}@Overridepublic <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {LOGGER.info("The retry is open.");System.out.println("The retry is open.");return super.open(context, callback);}}
测试retryTemplate.execute()
使用retryTemplate.execute() 调用需要需要自动重试的方法。
@Autowiredprivate RetryTemplate retryTemplate;@GetMapping("/retryTemplateTest")public Object retryTemplateTest(@RequestParam int num) throws Exception {try {Object result = retryTemplate.execute(arg -> testService.getRetryTemplateNum(num));return result;} catch (Exception e) {e.printStackTrace();}return null;}
重启项目,然后在浏览器中输入:
http://localhost:8090/common/test/retryTemplateTest?num=0
Retry问题分析
由于retry用到了aspect增强,所有会有aspect的坑,就是方法内部调用,会使aspect增强失效,那么retry当然也会失效。
public class demo {public void A() {B();}//这里B不会执行@Retryable(Exception.class)public void B() {throw new RuntimeException("retry...");}}
重试机制,不能在接口实现类里面写。所以要做重试,必须单独写个service。
maxAttemps参数解释的是说重试次数,测试的时候发现这个=1时,方法一共只执行了一次,=3时,一共只执行3次。
