This post is also available in English and alternative languages.Spring Retry 是一套轻量级的重试框架,它提供了一组用于处理可重试异常的工具和策略。Spring Retry 旨在简化开发人员处理因网络延迟、资源暂时不可用或其他瞬时故障导致的异常操作。
Spring Retry 的整体流程如下:
1. RetryTemplate
RetryTemplate 是 Spring Retry 框架的核心组件,它封装了重试操作的基本流程。通过 RetryTemplate,可以指定重试策略、回退策略、监听器等,以灵活地控制重试行为。
主要功能包括:
- 执行重试操作
- 指定重试策略
- 指定回退策略
- 注册重试监听器
- 获取重试上下文
2. RetryCallback 和 RecoveryCallback
RetryCallback 接口用于封装业务逻辑代码。在 RetryTemplate 执行重试操作时,会回调 RetryCallback 接口中的 doInRetry()
方法来执行业务逻辑。如果 doInRetry()
方法抛出异常,则 RetryTemplate 会根据指定的重试策略进行重试。
RecoveryCallback 接口用于封装兜底处理逻辑。当 RetryTemplate 经过指定的重试次数后仍然无法成功执行业务逻辑时,会回调 RecoveryCallback 接口中的 recover()
方法来执行兜底处理。
3. 重试策略(RetryPolicy)
重试策略决定了 RetryTemplate 如何进行重试操作。Spring Retry 提供了多种重试策略,包括:
- SimpleRetryPolicy:固定次数重试策略,默认重试最大次数为 3 次。
- NeverRetryPolicy:不允许重试,只允许调用 RetryCallback 接口一次。
- AlwaysRetryPolicy:无限重试,直到成功。
- TimeoutRetryPolicy:超时时间重试策略,在指定的超时时间内允许重试。
- CircuitBreakerRetryPolicy:具有熔断功能的重试策略,可以防止因频繁重试导致的资源耗尽。
4. 回退策略
回退策略决定了 RetryTemplate 在重试之间等待的时间间隔。Spring Retry 提供了多种回退策略,包括:
- FixedBackOffPolicy:固定时间回退策略,每次重试之间等待固定的时间间隔。
- ExponentialBackOffPolicy:指数退避策略,每次重试之间等待的时间间隔会呈指数增长。
- ExponentialRandomBackOffPolicy:指数随机退避策略,每次重试之间等待的时间间隔会在一个随机范围内呈指数增长。
5. 注解方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @Slf4j @Service public class RefuseService { @Retryable( value = {Exception.class}, // 指定需要重试的异常类型 maxAttempts = 3, // 最大重试次数 backoff = @Backoff(delay = 1000, multiplier = 2) // 重试间隔和退避策略 ) public void testRetryAnnotation() { log.info("RefuseService.testRetryAnnotation() 调用,{}", DateUtil.formatDateTime(new Date())); System.out.println(); try { int i = 1 / 0; } catch (Exception e) { log.error("errMsg:[{}]", e.getMessage()); throw e; } }
@Recover public void recover(Exception e) { log.error("RefuseService.testRetryAnnotation(),重试仍然失败了,执行兜底逻辑"); log.error(e.getMessage(), e); } }
|
1 2 3 4 5 6 7 8 9 10 11
| @Slf4j @SpringBootTest @RunWith(SpringRunner.class) public class ApplicationSpringBootExampleTest20 { @Resource private RefuseService refuseService; @Test public void test() { refuseService.testRetryAnnotation(); } }
|
注意:需要在启动类加上@EnableRetry
注解。
6. 模板方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| @Slf4j @Service public class RefuseService {
public void testRetryTemplate() { RetryTemplate retryTemplate = RetryTemplate.builder().retryOn(Exception.class).build();
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(); retryPolicy.setMaxAttempts(5); retryTemplate.setRetryPolicy(retryPolicy);
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy(); backOffPolicy.setInitialInterval(1000); backOffPolicy.setMultiplier(2); retryTemplate.setBackOffPolicy(backOffPolicy);
retryTemplate.execute( retryContext -> { log.info("RetryCount:{}", retryContext.getRetryCount()); log.info("RefuseService.testRetryTemplate() 调用,date:{}", DateUtil.formatDateTime(new Date())); try { int i = 1 / 0; } catch (Exception e) { log.error("errMsg:[{}]", e.getMessage()); throw e; } return StringUtils.EMPTY; }, retryContext -> { log.error("RefuseService.testRetryTemplate() 重试仍然失败了,errMsg:{}", retryContext.getLastThrowable().getMessage(), retryContext.getLastThrowable()); return StringUtils.EMPTY; }); } }
|
- line 8~10:设置简单重试策略,最大重试次数为5次,
RetryContext.getRetryCount()
的值是从 0 开始的,如果maxAttempts设置为 5 的话,那就最大值为 5-1=4。
1 2 3 4 5 6 7 8 9 10 11
| @Slf4j @SpringBootTest @RunWith(SpringRunner.class) public class ApplicationSpringBootExampleTest20 { @Resource private RefuseService refuseService; @Test public void test2() { refuseService.testRetryTemplate(); } }
|
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| c.y.example20.service.RefuseService : RetryCount:0 c.y.example20.service.RefuseService : RefuseService.testRetryTemplate() 调用,date:2024-04-17 13:10:39 c.y.example20.service.RefuseService : errMsg:[/ by zero]
c.y.example20.service.RefuseService : RetryCount:1 c.y.example20.service.RefuseService : RefuseService.testRetryTemplate() 调用,date:2024-04-17 13:10:40 c.y.example20.service.RefuseService : errMsg:[/ by zero]
c.y.example20.service.RefuseService : RetryCount:2 c.y.example20.service.RefuseService : RefuseService.testRetryTemplate() 调用,date:2024-04-17 13:10:42 c.y.example20.service.RefuseService : errMsg:[/ by zero]
c.y.example20.service.RefuseService : RetryCount:3 c.y.example20.service.RefuseService : RefuseService.testRetryTemplate() 调用,date:2024-04-17 13:10:46 c.y.example20.service.RefuseService : errMsg:[/ by zero]
c.y.example20.service.RefuseService : RetryCount:4 c.y.example20.service.RefuseService : RefuseService.testRetryTemplate() 调用,date:2024-04-17 13:10:54 c.y.example20.service.RefuseService : errMsg:[/ by zero]
c.y.example20.service.RefuseService : RefuseService.testRetryTemplate() 重试仍然失败了,errMsg:/ by zero
java.lang.ArithmeticException: / by zero at com.yxcheng.example20.service.RefuseService.lambda$testRetryTemplate$0(RefuseService.java:74) ...
|
7. 小结
注解方式使用方便,代码量较少。不过个人认为有个缺点,就是没有其他的方式能在 @Recover
注解中区分不同的兜底处理方法。兜底处理方法的区分主要是基于异常类型的。毕竟很多时候,不同的业务逻辑需要执行不同的兜底逻辑。而模板方式则可以设置不同兜底逻辑。
8. 依赖
1 2 3 4 5
| <dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> <version>1.3.4</version> </dependency>
|