函数重试方案比较

前言

日常开发中 不仅仅在rpc调用的时候 需要重试啥的
有时候 接第三方系统 也得做一些重试的处理
之前一直都是自己利用function写了一个工具类
现在把自己写的基本思路 和 guava retry 和spring retry 一起记录一下 方便后续自己选择使用

https://github.com/rholder/guava-retrying https://github.com/spring-projects/spring-retry https://www.cnblogs.com/jelly12345/p/15292568.html

示例

利用function简略封装
  • RetryUtils
package com.ming.example.retry;


import java.util.function.Supplier;

/**
 * 重试工具类
 *
 * @author ming
 * @date 2022-03-22 10:54:41
 */
public class RetryUtils {

    /**
     * 提供重试次数和间隔设置
     *
     * @param function 执行的函数 带返回值的
     * @param count    重试次数   本身会执行一次也就是执行 count+1次还未成功 则抛出runTimeException
     * @param interval 间隔时长  单位ms
     * @author ming
     * @date 2022-03-22 13:45:40
     */
    public static <T> T retry(Supplier<T> function, int count, long interval) {
        //当前执行次数
        int executeNumber = 0;
        while (executeNumber <= count) {
            try {
                return function.get();
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("执行函数当前第" + executeNumber + "次!");
            }
            try {
                Thread.sleep(interval);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            executeNumber++;
        }
        throw new RuntimeException("执行" + count + "次,无法成功调用!");
    }

}
  • 调用方式
    public static int FLAG = 0;

    /**
     * 自己通过 function 去写一个公共处理重试的逻辑
     * 写起来简单 但是很多地方 很僵硬 只适合对特定的一系列重试的处理
     *
     * @author ming
     * @date 2022-03-22 13:54:16
     */
    @Test
    public void myFunction() {
        System.out.println(RetryUtils.retry(() -> {
            if (FLAG < 3) {
                FLAG++;
                throw new RuntimeException("测试异常,当前标记值为" + FLAG);
            }
            return FLAG;
        }, 3, 1000L));
    }
guava-retrying

https://github.com/rholder/guava-retrying

  • pom配置
        <dependency>
            <groupId>com.github.rholder</groupId>
            <artifactId>guava-retrying</artifactId>
            <version>2.0.0</version>
        </dependency>
  • 调用方式
    public static int FLAG = 0;
    /**
     * guava  retry
     * 依旧的简单粗暴   和spring retry 主要的区别就是 增加了对于结果判断来重置重试 retryIfResult
     *https://github.com/rholder/guava-retrying
     * @author ming
     * @date 2022-03-22 15:52:26
     */
    @SneakyThrows
    @Test
    public void guava() {
        Retryer<Integer> retry = RetryerBuilder.<Integer>newBuilder()
                //配置重试监听器
//                .withRetryListener()
                //配置重试等待策略 默认是立即重试
//                .withWaitStrategy()
                //设置停止策略  默认不停止
//                .withStopStrategy()
                //配置阻塞策略  默认是sleep
//                .withBlockStrategy()
                //设置每一次重试的时间限制
//                .withAttemptTimeLimiter()
                //如果出现Exception 则重试
//                .retryIfException()
                //如果出现RuntimeException则重试
//                .retryIfRuntimeException()
                //如果出现指定的异常类型 则重试
//                .retryIfExceptionOfType()
                //如果返回结果满足表达式则重试
//                .retryIfResult()
                .retryIfResult(integer -> integer == null || integer <= 0)
                .retryIfExceptionOfType(IOException.class)
                .retryIfRuntimeException()
                .withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS))
                .build();

        Integer result = retry.call(() -> {
            if (FLAG < 2) {
                FLAG++;
                System.out.println("测试异常,当前标记值为" + FLAG);
                throw new RuntimeException("测试异常,当前标记值为" + FLAG);
            }
            return FLAG;
        });

        System.out.println("执行结果:" + result);
    }
spring-retry

https://github.com/spring-projects/spring-retry

  • pom依赖
        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
            <version>${version}</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${version}</version>
        </dependency>
  • 调用方式

RetryTemplate方式调用

    /**
     * 使用 spring  retry
     * https://github.com/spring-projects/spring-retry
     * https://www.cnblogs.com/jelly12345/p/15292568.html
     * BackOff:补偿值,一般指失败后多久进行重试的延迟值。
     * Sleeper:暂停应用的工具,通常用来应用补偿值。
     * BackOffPolicy:补偿策略,决定失败后如何确定补偿值。
     * RetryContext:重试上下文,代表了能被重试动作使用的资源。
     * RetryPolicy:重试策略,决定失败能否重试。
     * RecoveryCallback:定义一个动作recover,在重试耗尽后的动作。
     * RetryCallback:具体的重试动作。
     * RetryOperations:通过传递RetryCallback,进行重试操作。
     * RetryState:重试状态,通常包含一个重试的键值。
     * RetryStatistics和RetryListener,用来监控Retry的执行情况,并生成统计信息。
     * <p>
     * spring 可以注解 和 使用RetryTemplate 两种方式来
     *
     * <pre>
     * {@code
     *
     * @Configuration
     * @EnableRetry
     * public class Application {
     *
     *     @Bean
     *     public Service service() {
     *         return new Service();
     *     }
     *
     * }
     *
     * @Service
     * class Service {
     *     @Retryable(RemoteAccessException.class)
     *     public void service() {
     *         // ... do something
     *     }
     *     @Recover
     *     public void recover(RemoteAccessException e) {
     *        // ... panic
     *     }
     * }
     * }
     * </pre>
     *
     * @author ming
     * @date 2022-03-22 13:55:04
     */
    @SneakyThrows
    @Test
    public void spring() {
        RetryTemplate retryTemplate = RetryTemplate.builder()
                //超时时间 当前整个重试过程的超时时间
                //.withinMillis()
                //允许无限尝试
                //.infiniteRetry()
                //设置自定义的重试策略
                //.customPolicy()
                //设置自定义的退让策略
                //.customBackoff()
                //设置固定时间重试
                .fixedBackoff(1000L)
                //设置uniformRandomBackoff策略
                //.uniformRandomBackoff()
                //不暂停 立即重试
                //.noBackoff()
                //设置监听器
                .withListener(new MethodInvocationRetryListenerSupport() {
                    @Override
                    protected <T, E extends Throwable> void doOnError(RetryContext context, MethodInvocationRetryCallback<T, E> callback, Throwable throwable) {
                        System.out.println("执行函数出现异常!" + throwable.getMessage());
                        super.doOnError(context, callback, throwable);
                    }

                    @Override
                    protected <T, E extends Throwable> void doClose(RetryContext context, MethodInvocationRetryCallback<T, E> callback, Throwable throwable) {
                        System.out.println("关闭重试!");
                        super.doClose(context, callback, throwable);
                    }
                })
                //.withListeners()
                //重试上线  3 = 初始执行一次+两次重试
                .maxAttempts(3)
                //重试间隔策略 currentInterval = Math.min(initialInterval * Math.pow(multiplier, retryNum), maxInterval)
//                .exponentialBackoff(100,2,10000)
                //指定重试异常的类型
                .retryOn(Exception.class)
                //指定忽略重试异常的类型
                //.notRetryOn()
                //遍历异常处理  会尝试向上找继承关系
                .traversingCauses()
                .build();
        int result = retryTemplate.execute((RetryCallback<Integer, Throwable>) context -> {
            if (FLAG < 2) {
                FLAG++;
                System.out.println("测试异常,当前标记值为" + FLAG);
                throw new RuntimeException("测试异常,当前标记值为" + FLAG);
            }
            return FLAG;
        }, (RecoveryCallback<Integer>) context -> {
            //当重试耗尽的时候 进入此函数
            System.out.println("重试耗尽!!!!!!!");
            return 0;
        });
        System.out.println(result);

    }

注解调用方式

@Configuration
@EnableRetry
public class Application {

    @Bean
    public Service service() {
        return new Service();
    }

}

@Service
class Service {
    @Retryable(RemoteAccessException.class)
    public void service() {
        // ... do something
    }
    @Recover
    public void recover(RemoteAccessException e) {
       // ... panic
    }
}

总结

自己实现 可以按照对应的需求 实现的更加精细 示例只是个例子 需要自己去额外的处理其他的细节 guava-retrying和spring-retry 大多数使用方式一样 区别较大的地方 就是guava-retrying 支持根据结果去继续重试

© 2024 ming博客. All rights reserved.基于rust salvo性能猛的很!