大数跨境
0
0

百万级任务重试框架 Fast-Retry,太强了!

百万级任务重试框架 Fast-Retry,太强了! macrozheng
2025-10-20
0

Boot+Cloud项目学习:macrozheng.com

前言

假设你的系统里有100万个用户,然后你要轮询重试的获取每个用户的身份信息, 如果你还在使用SpringRetry和GuavaRetry 之类的这种单任务的同步重试框架,那你可能到猴年马月也处理不完, 即使加再多的机器和线程也是杯水车薪, 而Fast-Retry正是为这种场景而生

Fast-Retry

一个高性能的多任务重试框架,支持百万级任务的异步重试、以及支持编程式和注解声明式等多种使用方式、 也支持自定义结果重试逻辑。

What is this?

与主流的Spring-Retry, Guava-Retry等单任务同步重试框架不同,Fast-Retry是一个支持异步重试框架,支持异步任务的重试、超时等待、回调。 Spring-Retry, Guava-Retry均无法支持大批量任务的重试,即使加入线程池也无法解决,因为实际每个重试任务都是单独的同步逻辑,然后会会占用过多线程资源导致大量任务在等待处理,随着任务数的增加,系统吞吐量大大降低,性能指数级降低,而Fast-Retry在异步重试下的性能是前者的指数倍。

下图是三者的性能对比

  • 测试线程池:  8个固定线程
  • 单个任务逻辑:  轮询5次,隔2秒重试一次,总耗时10秒
  • 未测预计公式:  当我们使用线程池的时候, 一般线程池中 总任务处理耗时 =  任务数/并发度 x 单个任务重试耗时
任务数
FastRetry
Spring-Retry
Guava-Retry
1
10秒
10秒
10秒
10
10.066秒
20.092秒
20.078秒
50
10.061秒
70.186秒
70.168秒
100
10.077秒
130.33秒
130.31秒
500
10.154秒
631.420秒
631.53秒
1000
10.237秒
1254.78秒
1256.28秒
5000
10.482秒
没测预计:6250秒
没测预计:6250秒
1万
10.686秒
没测预计:12520秒
没测预计:12520秒
10万
13.71秒
没测预计:125000秒
没测预计:125000秒
50万
28.89秒
没测预计:625000秒
没测预计:625000秒
100万
58.05秒
没测预计:1250000秒
没测预计:1250000秒

可以看到即使是处理100万个任务,Fast-Retry的性能也比Spring-Retry和Guava-Retry处理在50个任务时的性能还要快的多的多属实降维打击,这么快的秘密在于除了是异步,重要的是当别人在重试间隔里休息的时候,Fast-Retry还在不停忙命的工作着。

即使抛开性能不谈, SpringRetry使用繁琐,不支持根据结果的进行重试,GuavaRetry虽然支持,但是又没有提供注解声明式的使用。

快速开始

引入依赖

<dependency>
        <groupId>io.github.burukeyou</groupId>
        <artifactId>fast-retry-all</artifactId>
        <version>0.2.0</version>
    </dependency>

有以下三种方式去构建我们的重试任务

这或许是一个对你有用的开源项目,mall项目是一套基于 SpringBoot3 + Vue 的电商系统(Github标星60K),后端支持多模块和 2024最新微服务架构 ,采用Docker和K8S部署。包括前台商城项目和后台管理系统,能支持完整的订单流程!涵盖商品、订单、购物车、权限、优惠券、会员、支付等功能!

  • Boot项目:https://github.com/macrozheng/mall
  • Cloud项目:https://github.com/macrozheng/mall-swarm
  • 教程网站:https://www.macrozheng.com

项目演示:

使用重试队列

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() {
                return2000;
            }

            // 执行重试,每次重试回调此方法
            @Override
            public boolean retry() {
                return ++result < 5;
            }

             // 获取重试结果
            @Override
            public String getResult() {
                return  result + "";
            }
        };
        CompletableFuture<String> future = queue.submit(task);
        log.info("任务结束 结果:{}",future.get());

使用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 Exception("test");
            //int i = 1/0;
            if (0 < 10){
                thrownew TimeoutException("test");
            }
            return"444";
        });

        String o = future.get();
        log.info("结果{}", o);

使用@FastRetry注解

底层还是使用的RetryQueue去处理, 只是帮我们简化了构建RetryTask的逻辑,并且与Spring进行整合能对Spring的bean标记了FastRetry注解的方法进行代理, 提供了重试任务注解声明式的使用方式

  • 依赖Spring环境,所以需要在Spring配置类加上@EnableFastRetry注解启用配置 , 这个@FastRetry注解的使用才会生效
  • 如果将结果类型使用CompletableFuture包装,自动进行异步轮询返回,否则同步阻塞等待重试结果。(推荐) 

下面定义等价于 RetryQueue.execute方法

// 如果发生异常,每隔两秒重试一次
    @FastRetry(retryWait = @RetryWait(delay = 2))
    public String retryTask(){
        return "success";
    }

下面定义等价于 RetryQueue.submit方法,支持异步轮询

@FastRetry(retryWait = @RetryWait(delay = 2))
    public CompletableFuture<String> retryTask(){
        return CompletableFuture.completedFuture("success");
    }

自定义重试注解

如果不喜欢或者需要更加通用化的贴近业务的重试注解,提供一些默认的参数和处理逻辑,可以自行定义一个重试注解并标记上@FastRetry并指定factory,然后实现AnnotationRetryTaskFactory接口实现自己的构建重试任务的逻辑即可。 @FastRetry默认实现就是: FastRetryAnnotationRetryTaskFactory

使用建议

无论是使用以上哪种方式去构建你的重试任务,都建议使用异步重试的方法,即返回结果是CompletableFuture的方法, 然后使用CompletableFuture的whenComplete方法去等待异步重试任务的执行结果。

对比案例

有一个天气服务的重试任务,需要重试N次才可能获取到某城市的天气情况。  分别使用Fast-Retry注解和Spring-Retry注解去并发获取1000个城市的天气情况,看下系统耗时。 同样的逻辑,Spring-Retry需要1256秒左右,Fast-Retry只需要10秒.左右

// 天气服务
@Component
publicclass WeatherService {
    
    // Fast-Retry  重试获取天气城市天气情况
    @FastRetry(
            maxAttempts = 100,
            retryWait = @RetryWait(delay = 2,timeUnit = TimeUnit.SECONDS))
    public CompletableFuture<WeatherResult> getFutureWeatherForCompare(String cityName){
        log.info("WeatherService进行重试  次数:{} 城市: {}",++index,cityName);
        WeatherResult weather = WeatherServer.getWeather(cityName);
        if (weather == null){
            //继续重试
            thrownew RuntimeException("模拟异常进行重试");
        }

        return FastRetryBuilder.of(weather);
    }

   // Spring-Retry  重试获取天气城市天气情况
    @Retryable(maxAttempts = 100,backoff = @Backoff(delay = 2000))
    public WeatherResult getSpringWeatherForCompare(String cityName){
        log.info("WeatherService进行重试  次数:{} 城市: {}",++index,cityName);
        WeatherResult weather = WeatherServer.getWeather(cityName);
        if (weather == null){
            //继续重试
            thrownew RuntimeException("模拟异常进行重试");
        }
        return weather;
    }

}

使用Spring-Retry去执行1000个重试任务

/**
 * spring-retry注解-测试
 * @throws Exception
 */

@Test
public void testFastRetryManyTaskForSpring() throws Exception {
    List<CompletableFuture<WeatherResult>> futures = new ArrayList<>();
    ExecutorService pool = Executors.newFixedThreadPool(8);

    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    int taskSize = 1000;
    for (int i = 0; i < taskSize; i++) {
        WeatherService taskWeatherService = context.getBean(WeatherService.class);
        CompletableFuture<WeatherResult> testFuture = new CompletableFuture<>();
        futures.add(testFuture);

        String cityName = "北京" + i;
        pool.execute(() -> {
            WeatherResult weather = taskWeatherService.getSpringWeatherForCompare(cityName);
            testFuture.complete(weather);
        });
    }

    CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

    System.out.println("所有任务完成");
    for (CompletableFuture<WeatherResult> future : futures) {
        WeatherResult weatherResult = future.get();
        log.info("城市轮询结束  result:{}",weatherResult.data);
    }

    stopWatch.stop();
    log.info("Spring-Retry测试总耗时  任务数:{} 耗时:{}",taskSize,stopWatch.getTotalTimeSeconds());
}

使用Fast-Retry去执行1000个重试任务

/**
     * 测试FastRetry注解测试
     * @throws Exception
     */

    @Test
    public  void testFastRetryManyTask() throws Exception {

        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        int taskSize = 1000;

        List<CompletableFuture<WeatherResult>> futures = new ArrayList<>();
        for (int i = 0; i < taskSize; i++) {
            WeatherService taskWeatherService = context.getBean(WeatherService.class);
            String cityName = "北京" + i;
            CompletableFuture<WeatherResult> weather = taskWeatherService.getFutureWeatherForCompare(cityName);
            futures.add(weather);
        }

        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

        System.out.println("所有任务完成");
        for (CompletableFuture<WeatherResult> future : futures) {
            WeatherResult weatherResult = future.get();
            log.info("城市轮询结束  result:{}",weatherResult.data);
        }

        stopWatch.stop();
        log.info("FastRetry测试总耗时  任务数:{} 耗时:{}",taskSize,stopWatch.getTotalTimeSeconds());
    }

项目地址

https://github.com/burukeYou/fast-retry

作者:李白的手机

来源:juejin.cn/post/7337989768637939739


Github上标星11K的微服务实战项目mall-swarm,全套 视频教程(2024最新版) 来了!全套教程约26小时,共59期,如果你想学习目前最新的微服务技术栈,同时提高自己微服务项目的开发能力的话,不妨了解下,下面是项目的整体架构图,感兴趣的小伙伴可以点击链接 mall-swarm视频教程 加入学习。

整套 视频教程 的内容还是非常完善的,涵盖Spring Cloud核心组件、微服务项目实战、Kubernetes容器化部署等内容,你也可以点击链接 mall-swarm视频教程 了解更多内容。

【声明】内容源于网络
0
0
macrozheng
内容 1937
粉丝 0
macrozheng
总阅读74
粉丝0
内容1.9k