目录
(一)有什么用?
在我们日常开发中经常会碰到调用其他第三方服务超时或者调用其他接口出现异常比如Excel导出超时出现异常,通常异常之后我们可以在用户测进行重新点击重新执行,但是这样不算太友好,因为仅仅可能是由于刚刚调用别的接口时网络出现了波动你要是在试一下可能就成功了,就轮不到用户在去重新点击,所以重试框架SpringRtry就解决了这个问题。
(二)什么是SpringRetry?
项目中为了保证处理更健壮,容错性更高,更不容易失败,使用自动重试的失败的操作,可提高后续操作的可用性,保证容错性。Spring实提供了自动重试机制,功能简单实用。当错误引起失败是暂时性的情况下,非常适用。比如操作中暂时的网络故障,或者数据库操作由暂时锁引起的异常等。
在微服务中通常都提供了重试与超时配置,比如SpringCloud的Feign组件。在SpringBoot的单应用项目中,我们则可以使用Spring提供的Spring Retry实现自动重试功能。
(三)怎么用?
-
使用@Retryable
1.pom文件加入依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
2.容器启动类上添加注解@EnableRetry
@EnableRetry
@SpringBootApplication
public class ApplicaitonMain {
public static void main(String[] args) {
SpringApplication.run(ApplicaitonMain.class, args);
}
}
3.ServiceImpl
@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;
}
@Recover
public int recover(Exception e) {
// 回调方法,业务逻辑处理
return -1;
}
}
4.重试注解说明@Retryable
@Retryable:标记当前方法使用重试机制
value:触发重试机制的条件,当遇到Exception时,会重试
maxAttempts :设置最大重试次数,默认为3次
delay:重试延迟时间,单位毫秒,即距离上一次重试方法的间隔
multiplier:delay重试延迟时间的间隔倍数,即第一次为5秒,第二次为5乘以2为10秒,依此类推
5.controoler
@GetMapping("/retryTest")
public String retryTest(@RequestParam int num) throws Exception {
int retrynum = testService.getRetryNum(num);
log.info("剩余数量===" + retrynum);
return "success";
}
6.运行测试
在浏览器中输入:http://localhost:8090/common/test/retryTest?num=0
可以看到,第一次用时5S,第二次用时10S,第三次用时15S;
最后会抛出异常,可以在上层代码进行try-catch处理相关业务逻辑;
也可以写回调方法处理,在TestServiceImpl类中添加如下代码,@Recover:当重试方法发生异常时,会执行该回调方法
7.兜底机制
@Recover
public int recover(Exception e) {
// 回调方法,业务逻辑处理
return -1;
}
使用RetryTemplate
1.配置RetryTemplate
首先配置RetryTemplate并注册为Bean交给Spring管理
@EnableRetry
@SpringBootApplication
public class QingfengApplication {
public static void main(String[] args) {
SpringApplication.run(QingfengApplication.class, args);
}
@Bean
public RetryTemplate retryTemplate() {
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;
}
}
setBackOffPeriod设置重试间隔2秒。
setMaxAttempts设置最大重试次数2次。
registerListener注册retryTemplate监听器。
2.创建监听器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;
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.");
System.out.println("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.");
System.out.println("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.");
System.out.println("The retry is open.");
return super.open(context, callback);
}
}
3.测试retryTemplate.execute()
使用retryTemplate.execute() 调用需要需要自动重试的方法。
@Autowired
private 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用到了AOP增强,所有会有AOP的坑,就是方法内部调用,会使AOP增强失效,那么retry当然也会失效。
public class demo {
public void A() {
B();
}
//这里B不会执行
@Retryable(Exception.class)
public void B() {
throw new RuntimeException("retry...");
}
}
重试机制,不能在接口实现类里面写。所以要做重试,必须单独写个service。