接口重试机制的最佳实践 - Guava-retrying的应用

项目开发中,调用第三方接口会因为网络延迟、异常导致调用的服务出错,重试几次可能就会调用成功(例如上传图片),所以需要一种重试机制进行接口重试来保证接口的正常执行。重试机制除了用代码实现外,guava-retry可以灵活的实现这一功能,
github 地址: https://github.com/rholder/guava-retrying/tree/master/src/main/java/com/github/rholder/retry

本文首先介绍如何用java代码实现接口的重试机制,然后介绍下guava-retry的使用。

一、Java自己实现接口重试机制

首先需要一个服务,比如图片上传服务:

 public boolean uploadPicture(String fileName, int count) {
        System.out.println("开始上传文件:" + fileName);
        // 模拟在第3次重试成功
        if (count == 3) {
            System.out.println("文件上传成功, 重试次数:" + count);
            return true;
        }
        // 模拟因网络等原因导致的图片上传服务超时
        return false;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在接口中模拟因网络故障,每次上传操作都是失败的,这个时候调用图片上传接口时需要重试3次,代码实现如下:

public void uploadFile(String fileName, int retryTimes) {
        PictureService pictureService = new PictureService();
        boolean success = pictureService.uploadPicture(fileName);
        if (retryTimes > 0) {
            int count = 1;
            while (!success && count <= retryTimes) {
                System.out.println("第-" + count + "-次重试");
                success = pictureService.uploadPicture(fileName);
                count++;

            }
        }
        return;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

测试:

public static void main(String[] args) {
        new Test2().uploadFile("testFile", 3);
}
  • 1
  • 2
  • 3

测试结果如下:
在这里插入图片描述

可以看到如果图片服务接口PictureService返回的结果为false,会进行重试,上述代码可以初步实现接口的重试。

二、接口重试的优雅实现 - guava-retry代码实战

上述代码虽然可以初步实现接口的重试,但是这样的代码不够优雅的同时,不能做到更好的去控制接口的重试,比如进行重试的条件是否可控、重试的次数、重试的策略选择等等,而guava-retry就提供了这种简便、可供实现的接口重试机制。
下面首先看看guava-retry是如何实现接口重试的。

1、pom.xml

首先引入jar包:


      com.github.rholder
      guava-retrying
      2.0.0

2、代码实现

package com.hrms.csdn;

import com.github.rholder.retry.Retryer;
import com.github.rholder.retry.RetryerBuilder;
import com.github.rholder.retry.StopStrategies;
import com.github.rholder.retry.WaitStrategies;
import com.google.common.base.Predicates;

import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

/**
 * Created by wanggenshen
 * Date: on 2019/1/4 00:42.
 * Description: XXX
 */
public class Test {

    static Retryer retryer;

   static {
        retryer = RetryerBuilder.newBuilder()
                .retryIfException() // 抛出异常会进行重试
                .retryIfResult(Predicates.equalTo(false)) // 如果接口返回的结果不符合预期,也需要重试
                .withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS)) // 重试策略, 此处设置的是重试间隔时间
                .withStopStrategy(StopStrategies.stopAfterAttempt(5)) // 重试次数
                .build();
    }


    public boolean uploadFile(String fileName) {
        try {

            return retryer.call(new Callable() {
                int count = 0;
                @Override
                public Boolean call() throws Exception {
                    return new PictureService().uploadPicture(fileName, count++);
                }
            });
        } catch (Exception e) {
        	e.printStackTrace();
            return false;
        }

    }


    public static void main(String[] args) {
        new Test().uploadFile("testFile");
    }
}


  •  

3、测试结果:

在这里插入图片描述

三、guava-retry使用介绍

通过代码实战我们了解到了guava-retry接口重试的实现,现在看看guava-retry的详细配置,以及如何更好的使用。

1、RetryerBuilder

RetryerBuilder是用来快速生成Retryer实例,并且可以配置重试次数、超时时间等。

2、retryIfException、retryIfRuntimeException、retryIfExceptionOfType

  • retryIfException
    retryIfException支持Exception异常对象,当抛出runtime异常、checked异常时都会重试,但是error不会重试。

  • retryIfRuntimeException
    retryIfRuntimeException只会在抛出Runtime异常的时候才会重试,checked异常和error都不重试。

  • retryIfExceptionOfType
    retryIfExceptionOfType允许我们只在发生特定异常的时候才重试,比如NullPointerException和IllegalStateException都属于runtime异常,也包括自定义的error

.retryIfExceptionOfType(Error.class)// 只在抛出error重试

出现指定异常时才会重试
.retryIfExceptionOfType(IllegalStateException.class) .retryIfExceptionOfType(NullPointerException.class)

3、retryIfResult

retryIfResult可以指定你的Callable方法在返回值的时候进行重试,如  
.retryIfResult(Predicates.equalTo(false)) // 返回false重试

4、WaitStrategy

等待时长策略,重试间隔时间。
常用的策略有:

  • FixedWaitStrategy 固定等待时长策略;
    .withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS)
  • RandomWaitStrategy 随机等待时长策略(可以提供一个最小和最大时长,等待时长为其区间随机值)。
  • IncrementingWaitStrategy 递增等待时长策略(提供一个初始值和步长,等待时间随重试次数增加而增加)。

5、StopStrategy

停止重试策略,提供三种:

  • StopAfterDelayStrategy 设定一个最长允许的执行时间;比如设定最长执行10s,无论任务执行次数,只要重试的时候超出了最长时间,则任务终止,并返回重试异常RetryException;
    .withStopStrategy(StopStrategies.stopAfterDelay(5, TimeUnit.SECONDS)) // 最长允许执行的时间

  • StopAfterAttemptStrategy 设定最大重试次数,如果超出最大重试次数则停止重试,并返回重试异常;
    .withStopStrategy(StopStrategies.stopAfterAttempt(5)) // 重试次数

  • NeverStopStrategy 不停止,用于需要一直轮训知道返回期望结果的情况。

6、RetryListener

自定义重试监听器,可以用于异步记录错误日志。当发生重试的时候,需要记录下重试的次数、结果等信息,或者有更多的拓展。

具体用法如下:

public class MyRetryListener implements RetryListener {


    @Override
    public  void onRetry(Attempt attempt) {
        // 距离上一次重试的时间间隔
        System.out.println("距上一次重试的间隔时间为:" + attempt.getDelaySinceFirstAttempt());
        // 重试次数
        System.out.println("重试次数: " + attempt.getAttemptNumber());
        // 重试过程是否有异常
        System.out.println("重试过程是否有异常:" + attempt.hasException());
        if (attempt.hasException()) {
            System.out.println("异常的原因:" + attempt.getExceptionCause().toString());
        }
        //重试正常返回的结果
        System.out.println("重试结果为:" + attempt.hasResult());

        System.out.println();
    }
}
static Retryer retryer;

 static {
        retryer = RetryerBuilder.newBuilder()
                .retryIfException() // 抛出异常会进行重试
                .retryIfResult(Predicates.equalTo(false)) // 如果接口返回的结果不符合预期,也需要重试
                .withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS)) // 重试策略, 此处设置的是重试间隔时间
                .withStopStrategy(StopStrategies.stopAfterAttempt(5)) // 重试次数
                .withRetryListener(new MyRetryListener())
                .build();
    }
  •  

最后的执行结果如下:
在这里插入图片描述

你可能感兴趣的:(springboot)