环境:SpringBoot3.2.5
1. 简介
业务功能重试机制通常在项目中是非常有必要的,特别是在处理外部系统调用(如HTTP请求、数据库操作、消息队列等)时。这些操作可能因网络波动、服务暂时不可用等原因而失败。重试机制可以提高系统的健壮性和用户体验,通过自动重试可以减少因单次失败导致的整体业务中断。本篇文章将介绍一款非常优秀的重试框架Fast-Retry。
Fast-Retry是一个高性能任务重试框架,支持百万级别任务的并发重试处理。与主流的Spring-Retry, Guava-Retry等同步重试框架不同,Fast-Retry是一个支持异步重试框架,支持异步任务的重试、超时等待、回调。Spring-Retry, Guava-Retry均无法支持大批量任务的重试,因为会占用过多线程资源导致大量任务在等待处理,随着任务数的增加,系统吞吐量大大降低,性能指数级降低,Fast-Retry的性能是前者的指数倍。
Fast-Retry,Spring-Retry,Guava-Retry性能对比
测试条件
- 测试线程池: 8个固定线程
- 单个任务逻辑: 轮询5次,隔2秒重试一次,总耗时10秒
- 未测预计公式:当我们使用线程池的时候, 一般线程池中 总任务处理耗时 = 任务数/并发度 x 单个任务重试耗时
图片
2. 实战案例
2.1 引入依赖
<dependency>
<groupId>io.github.burukeyou</groupId>
<artifactId>fast-retry-all</artifactId>
<version>0.2.0</version>
</dependency>
配置开启重试功能
@SpringBootApplication
@EnableFastRetry
public class SpringbootRetryApplication {}
接下来就可以通过@FastRetry注解配置类或方法。
2.2 基于编程重试
public String process() throws Exception {
// 自定义结果重试策略,如果返回结果不是"success"则进行重试
RetryResultPolicy<String> resultPolicy = result -> !result.equals("success");
FastRetryer<String> retryer = FastRetryBuilder.<String>builder()
// 重试次数
.attemptMaxTimes(2)
// 重试间隔
.waitRetryTime(1, TimeUnit.SECONDS)
// 发生异常后是否重试
.retryIfException(true)
// 什么类型的异常进行重试
.retryIfExceptionOfType(RuntimeException.class)
.exceptionRecover(true)
// 自定义结果重试策略
.resultPolicy(resultPolicy)
.build();
CompletableFuture<String> future = retryer.submit(() -> {
int r = new Random().nextInt(10) ;
System.out.printf("执行业务方法, 随机值: %d%n", r) ;
if (r != 1) {
// 抛出异常,也会重试
// throw new RuntimeException("错误的参数: " + r) ;
return "dead" ;
}
return "success" ;
});
return future.get();
}
运行结果
成功
执行业务方法, 随机值: 5
执行业务方法, 随机值: 4
执行业务方法, 随机值: 1
结果: success
失败
图片
超过重试次数后抛出异常,并且方法执行的最终结果返回:null。
2.3 基于注解
基于注解方式使用起来与spring-retry差不多。一个注解搞定。
@FastRetry(
retryWait = @RetryWait(delay = 2),
exceptionRecover = false,
maxAttempts = 2,
retryStrategy = PackRetryPolicy.class
)
public String business(Long id, String name) {
int r = new Random().nextInt(10) ;
System.out.printf("执行业务方法, 随机值: %d%n", r) ;
if (r != 1) {
throw new RuntimeException("错误的参数: " + r) ;
}
return "success" ;
}
自定义方法返回结果重试策略。
public class PackRetryPolicy implements RetryResultPolicy<String> {
public boolean canRetry(String t) {
return !t.equals("success") ;
}
}
结果重试策略可以有多个。
2.4 异步任务重试
@FastRetry(
retryWait = @RetryWait(delay = 2),
maxAttempts = 2,
retryStrategy = PackRetryPolicy.class
)
public CompletableFuture<String> asyncBusiness(Long id, String name) {
return CompletableFuture.supplyAsync(() -> {
System.out.println("async 执行业务方法...") ;
int r = new Random().nextInt(10) ;
if (r != 1) {
// throw new RuntimeException("错误的参数: " + r) ;
return "1" ;
}
return "success" ;
}) ;
}
输出结果
async 执行业务方法...
async 执行业务方法...
async 执行业务方法...
发生错误: com.burukeyou.retry.core.exceptions.FastRetryTimeOutException:
The maximum retry count has been exceeded after 2 times. Stop retry
同样的代码,如果换成spring-retry,如下:
// spring-retry的注解
@Retryable(maxAttempts = 2)
public CompletableFuture<String> asyncBusiness(Long id, String name) {
// 方法体与上面基本,一样只不过其中抛出的是异常
}
输出结果
async 执行业务方法...
发生错误: java.lang.RuntimeException: 错误的参数: 3
没有进行重试,说明spring-retry不支持异步任务。
在spring-retry中你可以在注解中配置recover,指定一个恢复的方法(或降级的方法),在fast-retry中没有这样的功能。如下spring-retry示例:
@Retryable(maxAttempts = 2, recover = "businessRecover")
public String business(Long id, String name) {}
@Recover
private String businessRecover(Throwable th, Long id, String name) {}
当重试次数用尽后,将调用我们这里配置的businessRecover方法,同时在该方法中还可以获取具体的异常信息。