当前位置: 代码迷 >> 综合 >> 深入浅出guava-retrying,新的重试机制
  详细解决方案

深入浅出guava-retrying,新的重试机制

热度:63   发布时间:2023-12-16 23:47:44.0

文章目录

    • 背景
    • 使用
      • 首先引入:
      • 简单的使用案例:
      • 返回的结果
    • Api详解
    • 项目中使用

背景

项目中使用阿里云进行文件上传等操作,有时候上传会因为网络问题导致超时;或者在调用一些三方的操作上会导致超时,这时候应该在程序中进行重试.平常重试我们会使用try-catch然后一些判断去进行重试,写起来极其不优雅,不方便.guava-retrying就是为了解决以上痛点的.

使用

可以先考到测试项目,执行一下基本就了解了

首先引入:

   <dependency><groupId>com.github.rholder</groupId><artifactId>guava-retrying</artifactId><version>2.0.0</version></dependency>

简单的使用案例:

import com.github.rholder.retry.*;
import lombok.extern.slf4j.Slf4j;import java.util.concurrent.TimeUnit;/*** @ClassName RetryerTest* @Author yida* @Date 2021/8/18 5:08 下午* @Description RetryerTest*/
@Slf4j
public class RetryerTest {
    private static int i = 0;public static void main(String[] args) throws Exception {
    System.out.println("请求结束返回:"+test());}public static Integer test() throws Exception {
    Retryer<Integer> retryer = RetryerBuilder.<Integer>newBuilder()// 抛出runtime异常、checked异常时都会重试,但是抛出error不会重试.retryIfException()//3秒后重试.withWaitStrategy(WaitStrategies.fixedWait(3000, TimeUnit.MILLISECONDS))//最多重试次数,后面可接入apollo.withStopStrategy(StopStrategies.stopAfterAttempt(3))// 执行时间不超过5秒.withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(5, TimeUnit.SECONDS))// 回调监听,不管成功失败都会执行listener.withRetryListener(new RetryListener() {
    @Overridepublic <Integer> void onRetry(Attempt<Integer> attempt) {
    System.out.println("listener开始....");// 第几次重试,(注意:第一次重试其实是第一次调用)System.out.print("[重试的次数]time=" + attempt.getAttemptNumber());// 距离第一次重试的延迟System.out.print(",重试的延迟delay=" + attempt.getDelaySinceFirstAttempt());// 重试结果: 是异常终止, 还是正常返回System.out.print(",hasException=" + attempt.hasException());System.out.print(",hasResult=" + attempt.hasResult());// 是什么原因导致异常if (attempt.hasException()) {
    System.out.print(",causeBy=" + attempt.getExceptionCause().toString());} else {
    // 正常返回时的结果System.out.print(",result=" + attempt.getResult());}// bad practice: 增加了额外的异常处理代码try {
    Integer result = attempt.get();System.out.println("在listener里面处理结果,result get=" + result);} catch (Exception e) {
    System.out.println("在listener里面处理异常exception." + e.getCause().toString());}System.out.println("listener结束....\n\r\n\r");}}).build();Integer call = retryer.call(() -> {
    System.out.println("同一规则,可以执行多份代码");return 0;});return retryer.call(() -> {
    // 前两次模拟异常,第三次成功if (i >= 2) {
    return 0;} else {
    i++;}try {
    // 模拟请求异常return 1 / 0;} catch (Exception e) {
    // 打印异常System.out.println("执行代码,请求失败" + e);throw e;}});}}

返回的结果

同一规则,可以执行多份代码
listener开始....
[重试的次数]time=1,重试的延迟delay=2,hasException=false,hasResult=true,result=0在listener里面处理结果,result get=0
listener结束....执行代码,请求失败java.lang.ArithmeticException: / by zero
listener开始....
[重试的次数]time=1,重试的延迟delay=0,hasException=true,hasResult=false,causeBy=java.lang.ArithmeticException: / by zero在listener里面处理异常exception.java.lang.ArithmeticException: / by zero
listener结束....执行代码,请求失败java.lang.ArithmeticException: / by zero
listener开始....
[重试的次数]time=2,重试的延迟delay=3006,hasException=true,hasResult=false,causeBy=java.lang.ArithmeticException: / by zero在listener里面处理异常exception.java.lang.ArithmeticException: / by zero
listener结束....listener开始....
[重试的次数]time=3,重试的延迟delay=6011,hasException=false,hasResult=true,result=0在listener里面处理结果,result get=0
listener结束....请求结束返回:0Process finished with exit code 0

Api详解

通过RetryerBuilder创建Retryer对象,使用Retryer对象执行call方法.Retryer对象可以执行多个call方法,不过是按照顺序执行的.所以一般使用都是通过RetryerBuilder新建Retryer对象去执行.
RetryerBuilder是一个factory建立者,能够定制设置重试源且能够支持多个重试源,能够配置重试次数或重试超时时间,以及能够配置等待时间间隔,建立重试者Retryer实例。
RetryerBuilder的重试源支持Exception异常对象 和自定义断言对象,经过retryIfException 和retryIfResult设置,同时支持多个且能兼容。

RetryerBuilder的部分代码:

public class RetryerBuilder<V> {
    private AttemptTimeLimiter<V> attemptTimeLimiter;private StopStrategy stopStrategy;private WaitStrategy waitStrategy;private BlockStrategy blockStrategy;private Predicate<Attempt<V>> rejectionPredicate = Predicates.alwaysFalse();private List<RetryListener> listeners = new ArrayList<RetryListener>();private RetryerBuilder() {
    }public static <V> RetryerBuilder<V> newBuilder() {
    return new RetryerBuilder<V>();}
// 省略

retryIfException,抛出runtime异常、checked异常时都会重试,可是抛出error不会重试。

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

.retryIfExceptionOfType(NullPointerException.class).retryIfExceptionOfType(IllegalStateException.class)

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

当发生重试以后,可使用RetryListener进行监听.能够注册多个RetryListener,会按照注册顺序依次调用。测试代码写的很清楚了.

项目中使用

可以通过aop对指定方法进行重试.
新建自定义重试注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface RetryAnnotation {
    }

然后写个切面对retryAnnotation进行切面处理.


@Component
@Aspect
@Slf4j
public class RetryAspect {
    @Around("@annotation(com.study.springbootplus.config.RetryAnnotation)")public Object process(ProceedingJoinPoint joinPoint) throws Exception {
    // 参数可以改到配置文件中Retryer<Object> retryer = RetryerBuilder.newBuilder()// 抛出runtime异常、checked异常时都会重试,但是抛出error不会重试.retryIfException()//3秒后重试.withWaitStrategy(WaitStrategies.fixedWait(3000, TimeUnit.MILLISECONDS))//最多重试次数.withStopStrategy(StopStrategies.stopAfterAttempt(3)).build();Object[] parameters = joinPoint.getArgs();return retryer.call(() -> {
    try {
    return joinPoint.proceed();} catch (Throwable ex) {
    log.error("RetryAspect processed error,parameters={},ex={}", JSON.toJSON(parameters), ex);if (ex instanceof Exception) {
    throw (Exception) ex;} else {
    throw new Exception(ex);}}});}}
  相关解决方案