重试功能轮子 + guava retry

140 阅读2分钟

3年前我在中通快递的时候,写过一个重试的功能。今天看了up主我是秦同学写的轮子,感觉有异曲同工之妙,在此做个总结

1. 利用模板方法写一个重试功能

/**
 * 锁重试工具类
 */
@Slf4j
public abstract class TemplateRetryingUtil<T> {
    /**
     * 指定重试次数,必须重写
     * @return
     */
    protected abstract int retryTimes();

    protected abstract Long waitTime();

    /**
     * 重试业务逻辑,需要用户自己实现
     */
    protected abstract T getDataByRetrying(Object... param);

    public T retry(Object... param) {
        // 获取重试次数
        int retryTimes = retryTimes();
        // 重试次数条件限制
        if (retryTimes <= 0){

            log.warn("重试次数必须大于0");
            throw new RuntimeException("重试次数必须大于0");
        }
        // 辅助判断是否重试成功
        boolean retryIsSuccess = Boolean.FALSE;
        T result = null;
        // 开始重试
        for (int cnt = 0; cnt < retryTimes; cnt++) {
            try {
                //TODO 测试代码 , 可以扔掉的
                if (cnt == 0){
                    throw new RuntimeException("测试异常");
                }
                // 调用重试逻辑
                result = this.getDataByRetrying(param);
                //设置重试成功的flag
                retryIsSuccess = true;
                break;
            } catch (Exception e) {
                log.info("第:{}次重试失败,方法参数是:{},异常信息是:{}",cnt + 1,param,e.getMessage());
                try {
                    Thread.sleep(waitTime());
                } catch (InterruptedException interruptedexception) {
                    log.warn("线程等待异常,异常信息是:{}", interruptedexception.getMessage());
                }
            }
        }
        // 查询正常
        if (retryIsSuccess) {
            return result;
        }
        log.info("重试失败");
        return null;
    }
}





public class TemplateRetryingUtilTest extends TemplateRetryingUtil<String> {

    public static void main(String[] args) {
        TemplateRetryingUtilTest TemplateRetryingUtilTest = new TemplateRetryingUtilTest();
        Object retry = TemplateRetryingUtilTest.retry();
        System.out.println("重试获得的结果为:"+retry);
        System.out.println("============================");
        TemplateRetryingUtilTest templateRetryingUtilTest1 = new TemplateRetryingUtilTest();
        Object retry1 = TemplateRetryingUtilTest.retry("a","b");
        System.out.println("重试获得的结果为:"+retry1);
    }

    @Override
    protected int retryTimes() {
        return 4;
    }

    @Override
    protected Long waitTime() {
        return 1000L;
    }

    @Override
    protected String getDataByRetrying(Object... param) {
        System.out.println("重试代码中.....");
        System.out.println("重试人工号"+new Random().nextInt(10));
        return "重试成功";
    }
}

利用lambda表达式回调重试,本质上也是模板方法


/**
 *定义重试任务的返回结果类型是什么
 */
public class LambdaRetryUtil<R> {
    //重试等待时间长度
    private long waitTime = 500L;
    //重试次数
    private int retryTimes = 3;
    // 返回结果
    private R result;
    // 需要重试的业务逻辑
    private Supplier<R> supplier;

    // 重试业务入口处
    public static <T> LambdaRetryUtil<T> fromTask(Supplier<T> supplier) {
        // 重试任务实力定义
        LambdaRetryUtil<T> retryUtil = new LambdaRetryUtil<>();
        retryUtil.setSupplier(supplier);
        return retryUtil;
    }

    public LambdaRetryUtil<R> setSupplier(Supplier<R> supplier) {
        this.supplier = supplier;
        return this;
    }

    public LambdaRetryUtil<R> retryTimes(int retryTimes) {
        this.retryTimes = retryTimes;
        return this;
    }

    public LambdaRetryUtil<R> retryWaitTime(long waitTime) {
        this.waitTime = waitTime;
        return this;
    }

    // 重试业务逻辑
    public Optional<R> getResult() {
        int curRetryTime = 1;
        // 辅助判断是否重试成功
        boolean retryIsSuccess = Boolean.FALSE;
        while (curRetryTime < retryTimes + 1) {
            try {
                // 执行逻辑
                result = this.supplier.get();
                if (curRetryTime <= 1){
                    curRetryTime++;
                    throw new RuntimeException("运行过程中发生了异常");
                }
                curRetryTime++;
                retryIsSuccess = Boolean.TRUE;
            } catch (Exception e) {
                if (curRetryTime < this.retryTimes) {
                    // 进行下一轮重试
                    try {
                        Thread.sleep(this.waitTime);
                    } catch (InterruptedException ex) {
                        throw new RuntimeException(ex);
                    }
                    continue;
                }
                // 异常且无需重试
                throw new RuntimeException(e);
            }
            // 检查是否运行成功
            if (retryIsSuccess) {
                // 运行成功,返回结果
                return (Optional<R>) Optional.ofNullable(result);
            }
        }
        // 运行到这里说明一次都没有成功
        return Optional.empty();
    }
}

public class LambdaRetryUtilTest {

    public static void main(String[] args) {

        Optional<Boolean> result = LambdaRetryUtil.<Boolean>fromTask(() -> {
                    return task();})
                .retryTimes(3)
                .retryWaitTime(500L)
                .getResult();

        Optional<Boolean> result1 = LambdaRetryUtil.<Boolean>fromTask(() -> {
                    return task();})
                .retryTimes(3)
                .retryWaitTime(500L)
                .getResult();

    }
    public static Boolean task(){
        System.out.println("测试");
        return Boolean.TRUE;
    }

}

guava retry

/**
 * @Author: dingyawu
 * @Description: TODO
 * @Date: Created in 16:42 2022/11/27
 */
public class TestGuavaRetry {
    /**
     *
     */
    private static Retryer<Integer> retryer = RetryerBuilder.<Integer>newBuilder()
            .retryIfException()
            .withStopStrategy(StopStrategies.stopAfterAttempt(3))
            .withWaitStrategy(WaitStrategies.fixedWait(3, TimeUnit.SECONDS))
            .build();

    @Test
    public void testRetry(){
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return doQuery();
            }
        };

        int result;
        try {
            result = retryer.call(callable);
        } catch (Exception e) {
            //多次充实不满足以后,就返回-1了
            result = -1;
        }
        System.out.println(result);
    }

    private int doQuery() {
        Random r = new Random(System.currentTimeMillis());
        int num = r.nextInt(5);
        System.out.println("query result " + num);

        if (num == 0) {
            return 0;
        } else if (num == 1) {
            System.out.println("DBException");
            throw new RuntimeException("DBException");
        } else if (num == 2) {
            System.out.println("IllegalArgumentException");
            throw new IllegalArgumentException("IllegalArgumentException");
        } else if (num == 3) {
            System.out.println("NullPointerException");
            throw new NullPointerException("NullPointerException");
        } else {
            System.out.println("IndexOutOfBoundsException");
            throw new IndexOutOfBoundsException("IndexOutOfBoundsException");
        }
    }
}