转载

如何优雅地进行方法重试

程序调用第三方接口可能会出现网络抖动、超时等异常情况,这时我们通常会想到当是重试。我们首先模拟一段业务逻辑,然后开始我们重试代码当编写

/**
     * 这个是需要执行的业务逻辑 
     * 定义了一个随机数,当低于阈值的时候,抛出异常 
     * 调用方catch住异常后进行重试
     */
    private void doSomething() {
        log.info("开始业务逻辑...");
        int random = RandomUtils.nextInt(100);
        log.info("随机数为:{}", random);
        if (random < 90) {
            log.info("随机数低于阈值,准备重试");
            throw new ServiceException("业务异常");
        }
        log.info("结束业务逻辑...");
    }
复制代码

1. 普通重试

最普通的重试逻辑,就是在调用方捕获到异常后,再次调用业务逻辑方法(递归),直到成功。 该方案简单粗暴

public void normal(int count) {
        if (count < 5) {
            count += 1;
            try {
                doSomething();
            } catch (Exception e) {
                //此处可以定时休眠一点时间再次重试
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
                //重试
                normal(count);
            }
        }
    }
复制代码

2. SpringRetry 普通模式

Spring-retry是一个开源工具包,该工具把重试操作模板定制化,可以设置重试策略和回退策略。同时重试执行实例保证线程安全,下面给出对应的示例代码

/**
     * Spring提供的Retry机制
     */
    public void springRetry() {
        // 构建重试模板实例
        RetryTemplate retryTemplate = new RetryTemplate();
        // 设置重试策略,主要设置重试次数和需要捕获的异常
        SimpleRetryPolicy policy = new SimpleRetryPolicy(5, Collections.singletonMap(Exception.class, true));
        // 设置重试回退操作策略,主要设置重试间隔时间
        ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
        //初始间隔
        backOffPolicy.setInitialInterval(1000);
        //最大间隔
        backOffPolicy.setMaxInterval(10 * 1000L);
        //递增倍数(即下次间隔是上次的多少倍)
        backOffPolicy.setMultiplier(2);

        retryTemplate.setRetryPolicy(policy);
        retryTemplate.setBackOffPolicy(backOffPolicy);
        // 通过RetryCallback 重试回调实例包装正常逻辑逻辑,第一次执行和重试执行执行的都是这段逻辑
        final RetryCallback<Object, Exception> retryCallback = context -> {
            log.info("开始重试,重试次数为: {}", context.getRetryCount());
            doSomething();
            return null;
        };
        // 通过RecoveryCallback 重试流程正常结束或者达到重试上限后的退出恢复操作实例
        final RecoveryCallback<Object> recoveryCallback = context -> {
            log.info("执行重试结束依然失败后的代码");
            return null;
        };
        try {
            // 由retryTemplate 执行execute方法开始逻辑执行
            retryTemplate.execute(retryCallback, recoveryCallback);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
复制代码

3. SpringRetry 注解版本

在上面的代码基础上,Spring提供了重试操作的注解,下面给出对应的示例代码。使用该方式的时候需要注意以下两点:

(1) 需要在入口类上添加@EnableRetry

(2) @Retryable只能出现在最外层的方法,同一个类中,当某个方法调用加类该注解的方法时,重试不生效

/**
     * Spring提供的Retry机制
     */
    @Retryable(value = ServiceException.class, maxAttempts = 3, backoff = @Backoff(delay = 1000, multiplier = 2))
    public void springRetryAnnotation() {
        doSomething();
    }

    /**
     * 重试失败后调用的方法(注意,需跟重处理方法在同一个类中)
     */
    @Recover
    public void recover(ServiceException e) {
        log.info("执行重试结束依然失败后的代码");
    }
复制代码

4. GuavaRetry版本

SpringRetry版本只能对异常进行重试,对于自定义对数据结构不能支持,如果有这方面需求的化,可以考虑用GuavaRetry进行重试,具体示例代码如下(SpringTime默认不集成,使用时需要手动添加maven依赖)

/**
     * guava提供的retry机制
     */
    public void guavaRetry() {
        //设置重试5次,同样可以设置重试超时时间
        StopStrategy stopStrategy = StopStrategies.stopAfterAttempt(5);
        //设置每次重试间隔
        WaitStrategy waitStrategy = WaitStrategies.exponentialWait(2, 10, TimeUnit.SECONDS);

        Retryer<Void> retryer = RetryerBuilder.<Void>newBuilder().retryIfException().withStopStrategy(stopStrategy)
                .withWaitStrategy(waitStrategy).build();
        try {
            retryer.call(() -> {
                doSomething();
                return null;
            });
        } catch (ExecutionException | RetryException e) {
            e.printStackTrace();
        }
    }
复制代码
原文  https://juejin.im/post/5cdb71f9f265da03ae74e60d
正文到此结束
Loading...