Spring实现了一套重试机制,功能简单实用。Spring Retry是从Spring Batch独立出来的一个功能,已经广泛应用于Spring Batch,Spring Integration, Spring for Apache Hadoop等Spring项目。
本文将讲述如何使用Spring Retry及其实现原理。
在调用一些第三方接口时候可能会因为网络或者服务方异常导致请求失败,这个时候可以通过重试解决这种问题。
以下面的简单的例子来了解 Retry的功能,下面有个doTask函数,执行该函数的时候如果出现异常则需要重试任务
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();
}
把doTask函数中的导致异常的代码注释再运行可以看到控制台没有打印重试的信息
从上面代码可以看出写一个任务重试的工具不难,感兴趣的同学可以通过AOP自己重试功能,Spring官方的Retry模块就是通过Aop+注解的方式这个功能,我就不造轮子写一遍了。
由于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.1.需要在springboot启动添加@EnableRetry注解开启对Retry的支持
@EnableRetry
@SpringBootApplication
public class RetryApplication {
}
3.2.定义需要重试任务的异常类型
public class CustomRecoveryException extends Exception{
public CustomRecoveryException(String error){
super(error);
}
}
3.3.在需要任务重试的函数上面添加注解
@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();
}
}
可以看到控制台只打印了三次日志,从这能确认任务共执行了三次,只重试了两次。
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();
}
}
可以看到测试得到的结果和注解的方式是一样的,都只执行了三次任务。