比Spring-Retry还快的百万级任务重试框架——Fast-Retry
前言
假设你的系统中有 100 万个用户,你需要轮询获取每个用户的身份信息。如果你仍然使用 Spring-Retry 或 Guava-Retry 这样的单任务同步重试框架,那可能需要花费大量的时间才能处理完这些任务。即使你增加更多的服务器和线程,效果仍然不尽如人意。这时,Fast-Retry 正是为了解决这种高并发、大规模任务重试场景而设计的。
Fast-Retry是什么?
Fast-Retry 是一个高性能的多任务重试框架,专为大规模异步任务重试场景而生。它支持百万级别的任务异步重试,提供了编程式和注解声明式的使用方式,用户还可以自定义结果判断逻辑来决定是否进行重试。与传统的 Spring-Retry、Guava-Retry 不同,Fast-Retry 能够在任务量大幅增加的情况下,保持优异的吞吐量和性能。
为什么需要Fast-Retry?
与 Spring-Retry 和 Guava-Retry 这样的单任务同步重试框架相比,Fast-Retry 具有显著的优势。后者在处理大批量任务时,容易因为单任务的同步逻辑占用大量线程资源,导致线程池饱和。随着任务数量的增加,系统的吞吐量会急剧下降,性能损失呈指数级。而 Fast-Retry 通过异步任务重试,显著提高了性能,尤其在面对大规模任务时,性能提升更为明显。
性能对比
在以下的测试环境下,我们对比了 Spring-Retry、Guava-Retry 和 Fast-Retry 的性能表现:
-
线程池配置:固定为 8 个线程
-
单个任务逻辑:每个任务轮询 5 次,每次间隔 2 秒,总耗时 10 秒
-
估算公式:当使用线程池处理任务时,任务总处理时间通常为:
总任务处理时间 = (任务数 / 并发数) x 单个任务耗时
下图展示了三者的性能对比:
即便处理 100 万个任务,Fast-Retry 的处理性能仍然远远优于 Spring-Retry 和 Guava-Retry 在处理 50 个任务时的表现。这种巨大的性能差距源于 Fast-Retry 的异步设计。当其他框架在等待重试间隔时,Fast-Retry 仍在高效地执行任务,确保系统资源得以充分利用。
性能优势的秘密
Fast-Retry 的性能优势不仅仅来自于异步化设计。更重要的是,它在重试间隔期间仍能高效工作,最大化利用系统资源。而其他框架在重试间隔内是处于“休息”状态,这使得它们的性能在大任务量下显著下降。
快速开始
1. 引入依赖
首先,在你的 Maven 项目中添加以下依赖:
<dependency>
<groupId>io.github.burukeyou</groupId>
<artifactId>fast-retry-all</artifactId>
<version>0.2.0</version>
</dependency>
2. 使用重试任务的三种方式
2.1 使用重试队列
RetryTask就是可以配置我们重试任务的一些逻辑,比如怎么重试,怎么获取重试结果,隔多久后重试,在什么情况下重试。它可以帮助我们更加自由的去构建重试任务的逻辑。但如果只是简单使用,强烈建议使用FastRetryBuilder 或者 @FastRetry注解RetryQueue就是一个执行和调度我们重试任务的核心角色,其在使用上与线程池的API方法基本一致
ExecutorService executorService = Executors.newFixedThreadPool(8);
RetryQueue queue = new FastRetryQueue(executorService);
RetryTask<String> task = new RetryTask<String>() {
int result = 0;
// 定义重试间隔时间
@Override
public long waitRetryTime() {
return 2000; // 2 秒
}
// 定义重试逻辑
@Override
public boolean retry() {
return ++result < 5; // 重试5次
}
// 获取重试结果
@Override
public String getResult() {
return result + "";
}
};
CompletableFuture<String> future = queue.submit(task);
log.info("任务结束,结果: {}", future.get());
2.2 使用FastRetryBuilder
底层还是使用的RetryQueue去处理, 只是帮我们简化了构建RetryTask的逻辑。FastRetryBuilder
对 RetryQueue
进行了封装,简化了 RetryTask
的创建过程。它提供了一种更加简洁的方式来定义重试任务,并且支持自定义重试条件、最大重试次数、异常处理等。
RetryResultPolicy<String> resultPolicy = result -> result.equals("444");
FastRetryer<String> retryer = FastRetryBuilder.<String>builder()
.attemptMaxTimes(3)
.waitRetryTime(3, TimeUnit.SECONDS)
.retryIfException(true)
.retryIfExceptionOfType(TimeoutException.class)
.exceptionRecover(true)
.resultPolicy(resultPolicy)
.build();
CompletableFuture<String> future = retryer.submit(() -> {
log.info("正在重试...");
throw new TimeoutException("测试超时");
return "444";
});
String result = future.get();
log.info("最终结果: {}", result);
2.3 使用@FastRetry注解
通过 @FastRetry
注解,可以更方便地与 Spring 整合,实现注解声明式的重试逻辑。需要在 Spring 配置类中添加 @EnableFastRetry
注解来启用 Fast-Retry 功能。
对于异步重试,推荐使用 CompletableFuture
包装返回结果,从而实现异步非阻塞的轮询。
// 同步重试,每隔 2 秒重试一次
@FastRetry(retryWait = @RetryWait(delay = 2))
public String retryTask() {
return "success";
}
// 异步重试,返回 CompletableFuture
@FastRetry(retryWait = @RetryWait(delay = 2))
public CompletableFuture<String> asyncRetryTask() {
return CompletableFuture.completedFuture("success");
}
3. 自定义重试注解
如果你需要基于业务需求定制化的重试注解,可以自行定义一个新的注解,并使用 @FastRetry
进行标记。同时,实现 AnnotationRetryTaskFactory
接口,来定义自己的重试任务构建逻辑。
使用建议
无论你选择哪种方式构建重试任务,建议采用异步重试,即返回结果类型为 CompletableFuture
。你可以使用 CompletableFuture
的 whenComplete
方法来异步等待重试任务的执行结果。这种方式不仅可以提升系统的性能,还能更好地应对高并发任务场景。
评论区