目 录CONTENT

文章目录

比Spring-Retry还快的百万级任务重试框架——Fast-Retry

在等晚風吹
2024-10-25 / 0 评论 / 0 点赞 / 17 阅读 / 0 字 / 正在检测是否收录...

比Spring-Retry还快的百万级任务重试框架——Fast-Retry

前言

假设你的系统中有 100 万个用户,你需要轮询获取每个用户的身份信息。如果你仍然使用 Spring-RetryGuava-Retry 这样的单任务同步重试框架,那可能需要花费大量的时间才能处理完这些任务。即使你增加更多的服务器和线程,效果仍然不尽如人意。这时,Fast-Retry 正是为了解决这种高并发、大规模任务重试场景而设计的。


Fast-Retry是什么?

Fast-Retry 是一个高性能的多任务重试框架,专为大规模异步任务重试场景而生。它支持百万级别的任务异步重试,提供了编程式和注解声明式的使用方式,用户还可以自定义结果判断逻辑来决定是否进行重试。与传统的 Spring-Retry、Guava-Retry 不同,Fast-Retry 能够在任务量大幅增加的情况下,保持优异的吞吐量和性能。

为什么需要Fast-Retry?

与 Spring-Retry 和 Guava-Retry 这样的单任务同步重试框架相比,Fast-Retry 具有显著的优势。后者在处理大批量任务时,容易因为单任务的同步逻辑占用大量线程资源,导致线程池饱和。随着任务数量的增加,系统的吞吐量会急剧下降,性能损失呈指数级。而 Fast-Retry 通过异步任务重试,显著提高了性能,尤其在面对大规模任务时,性能提升更为明显。


性能对比

在以下的测试环境下,我们对比了 Spring-RetryGuava-RetryFast-Retry 的性能表现:

  • 线程池配置:固定为 8 个线程

  • 单个任务逻辑:每个任务轮询 5 次,每次间隔 2 秒,总耗时 10 秒

  • 估算公式:当使用线程池处理任务时,任务总处理时间通常为:

    总任务处理时间 = (任务数 / 并发数) x 单个任务耗时
    

下图展示了三者的性能对比:

1.png

2.png

即便处理 100 万个任务,Fast-Retry 的处理性能仍然远远优于 Spring-RetryGuava-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的逻辑。FastRetryBuilderRetryQueue 进行了封装,简化了 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。你可以使用 CompletableFuturewhenComplete 方法来异步等待重试任务的执行结果。这种方式不仅可以提升系统的性能,还能更好地应对高并发任务场景。

0

评论区