重试框架Spring-Retry 和 Guava-Retry的使用

目录

Spring-Retry

一、RetryTemplate

1、添加依赖

2、编写配置

1)RetryPolicy : 点进源码中我们能看到这个属性是一个 RetryPolicy 重试策略

2)BackOffPolicy : 点进源码里发现是一个 BackOffPolicy 回退策略

3、编写方法

前提:我们需要考虑到一个方式的三种情况

思路:需要一个方法内随机出现以上各种情况

实现:思路我们有了就开始代码方面

① service层方法

② Controller 层

二、注解方式

1、添加依赖

2、启动类启动注解

3、编写方法

① service层

@Retryable

@Backoff

 @Recover

② Controller层

 Guava-Retry

1、添加依赖

2、编写方法

① service层

② Controller层

3、实现原理

Retryer 接口

RetryerBuilder 类

重试条件

等待策略

停止策略

执行重试

总结


Spring-Retry

 Git:GitHub - spring-projects/spring-retry

Spring Retry是Spring框架的一个模块,它提供了一种简单且可配置的方式来在方法执行失败时进行重试。这对于处理网络通信、数据库连接、外部服务调用等不稳定操作非常有用。使用Spring Retry,您可以在失败的情况下自动重试方法,而无需手动编写复杂的重试逻辑。

这里以maven项目为例,展示两种使用方式

一、RetryTemplate

1、添加依赖

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>1.3.1</version>
</dependency>

2、编写配置

官方给的示例,你可以直接在psvm中测试

// Set the max attempts including the initial attempt before retrying
// and retry on all exceptions (this is the default):
SimpleRetryPolicy policy = new SimpleRetryPolicy(5, Collections.singletonMap(Exception.class, true));

// Use the policy...
RetryTemplate template = new RetryTemplate();
template.setRetryPolicy(policy);
template.execute(new RetryCallback<Foo>() {
    public Foo doWithRetry(RetryContext context) {
        // business logic here
    }
});

这里我是单独配置了一遍,然后使用三层的方式调用,你可以选择直接使用默认的 RetryTemplate

首先自定义一个异常,用于重试异常

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ServiceException extends RuntimeException {

    private int code;
    private String msg;
}

 编写RetryTemplate

@Bean("springRetryTemplate")
public RetryTemplate springRetryTemplate() {
    Map<Class<? extends Throwable>, Boolean> exceptionMap = new HashMap<>();
    exceptionMap.put(ServiceException.class, true);
    RetryTemplate retryTemplate = new RetryTemplate();
    // 重试策略,参数依次代表:最大尝试次数、可重试的异常映射
    SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(3, exceptionMap);
    retryTemplate.setRetryPolicy(retryPolicy);
    // 重试间隔时间
    FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
    backOffPolicy.setBackOffPeriod(1000L);
    retryTemplate.setBackOffPolicy(backOffPolicy);
    return retryTemplate;
}

这里解释下,RetryTemplate这里赋值了两个属性

1)RetryPolicy : 点进源码中我们能看到这个属性是一个 RetryPolicy 重试策略

private volatile RetryPolicy retryPolicy = new SimpleRetryPolicy(3);

可以看到有这么多的重试策略,默认是 SimpleRetryPolicy

  • NeverRetryPolicy: 只允许调用RetryCallback一次,不允许重试

  • AlwaysRetryPolicy: 允许无限重试,直到成功,此方式逻辑不当会导致死循环

  • SimpleRetryPolicy: 固定次数重试策略,默认重试最大次数为3次,RetryTemplate默认使用的策略

  • TimeoutRetryPolicy: 超时时间重试策略,默认超时时间为1秒,在指定的超时时间内允许重试

  • ExceptionClassifierRetryPolicy: 设置不同异常的重试策略,类似组合重试策略,区别在于这里只区分不同异常的重试

  • CircuitBreakerRetryPolicy: 有熔断功能的重试策略,需设置3个参数openTimeoutresetTimeoutdelegate

  • CompositeRetryPolicy: 组合重试策略,有两种组合方式,乐观组合重试策略是指只要有一个策略允许即可以重试,悲观组合重试策略是指只要有一个策略不允许即可以重试,但不管哪种组合方式,组合中的每一个策略都会执行

 点进 SimpleRetryPolicy 类中看看

2)BackOffPolicy : 点进源码里发现是一个 BackOffPolicy 回退策略

private volatile BackOffPolicy backOffPolicy = new NoBackOffPolicy();

 也是给出了很多的回退策略,默认使用的是 NoBackOffPolicy

 这里以 FixedBackOffPolicy 为例使用

  • NoBackOffPolicy: 无退避算法策略,每次重试时立即重试

  • FixedBackOffPolicy: 固定时间的退避策略,需设置参数sleeperbackOffPeriodsleeper指定等待策略,默认是Thread.sleep,即线程休眠,backOffPeriod指定休眠时间,默认1秒

  • UniformRandomBackOffPolicy: 随机时间退避策略,需设置sleeperminBackOffPeriodmaxBackOffPeriod,该策略在minBackOffPeriod,maxBackOffPeriod之间取一个随机休眠时间,minBackOffPeriod默认500毫秒,maxBackOffPeriod默认1500毫秒

  • ExponentialBackOffPolicy: 指数退避策略,需设置参数sleeperinitialIntervalmaxIntervalmultiplier,initialInterval指定初始休眠时间,默认100毫秒,maxInterval指定最大休眠时间,默认30秒,multiplier指定乘数,即下一次休眠时间为当前休眠时间*multiplier

  • ExponentialRandomBackOffPolicy: 随机指数退避策略,引入随机乘数可以实现随机乘数回退

3、编写方法

前提:我们需要考虑到一个方式的三种情况

  • 方法正常返回成功或错误,这里指的是流程正常走下来没有任何异常或者在某一步失败了,但是我们捕捉到了并进行返回
  • 方法抛出自定义异常,如比在调用远程方式的时候失败了,但是我们捕捉到了并进行了抛出相应的异常
  • 方法抛出未捕捉到的异常,比如空指针、索引超出界限等。

思路:需要一个方法内随机出现以上各种情况

我们可以指定一个随机数,然后判断这个随机数的区间,针对不同区间值进行相应的返回

实现:思路我们有了就开始代码方面

① service层方法
@Slf4j
@Service
public class DemoTaskService {

    @Resource
    @Qualifier("springRetryTemplate")
    private RetryTemplate retryTemplate;


    public boolean testTask(String param) {
        Boolean executeResult = retryTemplate.execute(retryCallback -> {
            boolean result = randomResult(param);
            log.info("调用的结果:{}", result);
            return result;
        }, recoveryCallback -> {
            log.info("已达到最大重试次数或抛出了不在重试策略内的异常");
            return false;
        });
        log.info("执行结果:{}", executeResult);
        return executeResult;
    }


    /**
     * 根据随机数模拟各种情况
     */
    public boolean randomResult(String param) {
        log.info("进入测试任务,参数:{}", param);

        int i = new Random().nextInt(10);
        log.info("随机生成的数:{}", i);
        switch (i) {
            case 0: {
                log.info("数字为0,返回成功");
                return true;
            }
            case 1: {
                log.info("数字为1,返回失败");
                return false;
            }
            case 2:
            case 3:
            case 4:
            case 5: {
                log.info("数字为2、3、4、5,抛出参数异常");
                throw new IllegalArgumentException("参数异常");
            }
            default: {
                log.info("数字大于5,抛出自定义异常");
                throw new ServiceException(10010, "远程调用失败");
            }
        }
    }

}
② Controller 层
@RestController
@RequestMapping("/retry")
@RequiredArgsConstructor
public class RetryController {

    private final DemoTaskService demoTaskService;

    @GetMapping("/spring")
    public Boolean springRetry(@RequestParam("param") String param) {
        return demoTaskService.testTask(param);
    }

}

访问 http://ip:port/retry/spring?param=str 模拟测试如下:  

1、遇到参数异常,因为参数异常不在重试异常中,所以不重试

2、 遇到正常返回true或false,不重试 

3、 遇到重试的异常,达到重试次数或不重试的异常

  

二、注解方式

1、添加依赖

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>1.3.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>${spring-boot.version}</version>
</dependency>

2、启动类启动注解

@EnableRetry
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

3、编写方法

① service层

@Slf4j
@Service
public class DemoTaskService {


    /**
     * 重试所调用方法
     */
    @Retryable(value = {ServiceException.class}, maxAttempts = 3, backoff = @Backoff(delay = 2000L, multiplier = 2))
    public boolean annotationTestTask(String param) {
        return randomResult(param);
    }

    /**
     * 根据相同参数和相同返回值匹配
     */
    @Recover
    public boolean annotationRecover(Exception e, String param) {
        log.error("参数:{},已达到最大重试次数或抛出了不在重试策略内的异常:", param, e);
        return false;
    }


    /**
     * 根据随机数模拟各种情况
     */
    public boolean randomResult(String param) {
        log.info("进入测试任务,参数:{}", param);

        int i = new Random().nextInt(10);
        log.info("随机生成的数:{}", i);
        switch (i) {
            case 0: {
                log.info("数字为0,返回成功");
                return true;
            }
            case 1: {
                log.info("数字为1,返回失败");
                return false;
            }
            case 2:
            case 3:
            case 4:
            case 5: {
                log.info("数字为2、3、4、5,抛出参数异常");
                throw new IllegalArgumentException("参数异常");
            }
            default: {
                log.info("数字大于5,抛出自定义异常");
                throw new ServiceException(10010, "远程调用失败");
            }
        }
    }

}
@Retryable

maxAttempts :最大重试次数,默认为3

value:抛出指定异常才会重试

include:和value一样,默认为空,当exclude也为空时,默认所有异常

exclude:指定不处理的异常

backoff:重试等待策略,默认使用@Backoff,@Backoff的value默认为1000L

@Backoff

value:隔多少毫秒后重试,默认为1000L

delay:和value一样,默认为0

multiplier(指定延迟倍数)默认为0,表示固定暂停1秒后进行重试,如果把multiplier设置为1.5,则第一次重试为2秒,第二次为3秒,第三次为4.5秒

 @Recover

当重试到达指定次数时,被注解的方法将被回调,可以在该方法中进行日志处理。需要注意的是发生的异常和入参类型一致时才会回调

② Controller层

@GetMapping("/spring/annotation")
public Boolean springRetryAnnotation(@RequestParam("param") String param) {
    return demoTaskService.annotationTestTask(param);
}

 访问 http://ip:port/retry/spring/annotation?param=str  模拟测试如下: 

1、遇到参数异常,因为参数异常不在重试异常中,所以不重试,执行 annotationRecover 方法

 2、遇到正常返回true或false,不重试 

 3、遇到重试的异常,达到重试次数或者不重试的异常,执行 annotationRecover 方法

  

 Guava-Retry

Git:GitHub - rholder/guava-retrying: This is a small extension to Google's Guava library to allow for the creation of configurable retrying strategies for an arbitrary function call, such as something that talks to a remote service with flaky uptime.

1、添加依赖

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

2、编写方法

① service层

@Slf4j
@Service
public class DemoTaskService {

    @Resource
    @Qualifier("springRetryTemplate")
    private RetryTemplate retryTemplate;

    public boolean guavaTestTask(String param) {
        // 构建重试实例 可以设置重试源且可以支持多个重试源 可以配置重试次数或重试超时时间,以及可以配置等待时间间隔
        Retryer<Boolean> retriever = RetryerBuilder.<Boolean>newBuilder()
                // 重试的异常类以及子类
                .retryIfExceptionOfType(ServiceException.class)
                // 根据返回值进行重试
                .retryIfResult(result -> !result)
                // 设置等待间隔时间,每次请求间隔1s
                .withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS))
                // 设置最大重试次数,尝试请求3次
                .withStopStrategy(StopStrategies.stopAfterAttempt(3))
                .build();
        try {
            return retriever.call(() -> randomResult(param));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }



    /**
     * 根据随机数模拟各种情况
     */
    public boolean randomResult(String param) {
        log.info("进入测试任务,参数:{}", param);

        int i = new Random().nextInt(10);
        log.info("随机生成的数:{}", i);
        switch (i) {
            case 0: {
                log.info("数字为0,返回成功");
                return true;
            }
            case 1: {
                log.info("数字为1,返回失败");
                return false;
            }
            case 2:
            case 3:
            case 4:
            case 5: {
                log.info("数字为2、3、4、5,抛出参数异常");
                throw new IllegalArgumentException("参数异常");
            }
            default: {
                log.info("数字大于5,抛出自定义异常");
                throw new ServiceException(10010, "远程调用失败");
            }
        }
    }

}

② Controller层

@RestController
@RequestMapping("/retry")
@RequiredArgsConstructor
public class RetryController {

    private final DemoTaskService demoTaskService;

    @GetMapping("/spring")
    public Boolean springRetry(@RequestParam("param") String param) {
        return demoTaskService.testTask(param);
    }

    @GetMapping("/spring/annotation")
    public Boolean springRetryAnnotation(@RequestParam("param") String param) {
        return demoTaskService.annotationTestTask(param);
    }

    @GetMapping("/guava")
    public Boolean guavaRetry(@RequestParam("param") String param) {
        return demoTaskService.guavaTestTask(param);
    }


}

 访问 http://ip:port/retry/guava?param=str  模拟测试如下: 

1、遇到参数异常,因为参数异常不在重试异常中,所以不重试

 2、遇到正常返回true或false,不重试 

 3、遇到重试的异常,达到重试次数或者不重试的异常

3、实现原理

Retryer 接口

定义了执行方法重试的方法,并提供了多个配置方法来设置重试条件、等待策略、停止策略等。它包含一个call方法,将需要重试的操作以Callable形式传递给它

public V call(Callable<V> callable) throws ExecutionException, RetryException {
    long startTime = System.nanoTime();
    //根据attemptNumber进行循环次数
    for (int attemptNumber = 1; ; attemptNumber++) {
        // 进入方法不等待,立即执行一次
        Attempt<V> attempt;
        try {
            // 执行callable中的具体业务
            // attemptTimeLimiter限制了每次尝试等待的时长
            V result = attemptTimeLimiter.call(callable);
            // 利用调用结果构造新的attempt
            attempt = new ResultAttempt<V>(result, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));
        } catch (Throwable t) {
            attempt = new ExceptionAttempt<V>(t, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));
        }
        // 遍历自定义的监听器
        for (RetryListener listener : listeners) {
            listener.onRetry(attempt);
        }
        // 判断是否满足重试条件,来决定是否继续等待并进行重试
        if (!rejectionPredicate.apply(attempt)) {
            return attempt.get();
        }
        // 此时满足停止策略,因为还没有得到想要的结果,因此抛出异常
        if (stopStrategy.shouldStop(attempt)) {
            throw new RetryException(attemptNumber, attempt);
        } else {
            // 行默认的停止策略——线程休眠
            long sleepTime = waitStrategy.computeSleepTime(attempt);
            try {
                // 也可以执行定义的停止策略
                blockStrategy.block(sleepTime);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RetryException(attemptNumber, attempt);
            }
        }
    }
}

RetryerBuilder 类

创建Retryer实例的构建器类。您可以通过RetryerBuilder配置重试策略、条件和其他参数,最终构建出一个Retryer实例

重试条件

允许您根据结果和异常类型来定义重试条件。例如,您可以设置当返回结果为特定值、或者当发生特定类型的异常时进行重试

等待策略

等待策略定义了每次重试之间的等待时间。Guava-Retry提供了多种等待策略,包括固定等待、指数等待等。可以根据情况选择适合的等待策略

停止策略

停止策略决定在何时停止重试。可以定义最大重试次数、重试时间限制等。一旦达到停止条件,重试将不再执行

执行重试

当构建好了Retryer实例并设置好相关策略后,可以将需要重试的操作(以Callable形式)传递给Retryercall方法。Retryer将根据配置的策略执行方法重试,直到达到停止条件或重试成功

总结

Guava-RetrySpring Retry都是用于在Java应用程序中实现方法重试的库,但它们来自不同的框架,并在一些方面有所不同。以下是它们之间的一些比较:

  1. 框架来源

    • Guava-RetryGuava-Retry是Google Guava库的一部分,它提供了一种用于重试操作的机制。
    • Spring RetrySpring Retry是Spring框架的一个模块,专门用于在Spring应用程序中实现重试逻辑。
  2. 库依赖

    • Guava-Retry:您需要添加Guava库的依赖来使用Guava-Retry
    • Spring Retry:您需要添加spring-retry模块的依赖来使用Spring Retry
  3. 配置和注解

    • Guava-Retry:重试逻辑通过构建Retryer实例并定义重试条件、等待策略等来配置。
    • Spring RetrySpring Retry提供了注解(如@Retryable@Recover等)和编程式配置来实现重试逻辑。
  4. 重试策略

    • Guava-RetryGuava-Retry允许您基于结果和异常类型来定义重试条件。您可以使用RetryerBuilder来自定义重试策略。
    • Spring RetrySpring Retry允许您使用注解来定义重试条件和相关属性,如最大重试次数、重试间隔等。
  5. 等待策略

    • Guava-RetryGuava-Retry提供了不同的等待策略(如固定等待、指数等待等),您可以选择适合您的应用程序的策略。
    • Spring RetrySpring Retry允许您通过注解或编程式配置来指定等待时间。
  6. 适用范围

    • Guava-Retry:可以用于任何Java应用程序,不仅限于Spring框架。
    • Spring Retry:专门设计用于Spring应用程序中,可以与其他Spring功能(如Spring AOP)集成。
  7. 依赖性

    • Guava-Retry:相对较轻量级,如果您只需要重试功能,可以考虑使用Guava库的一部分。
    • Spring Retry:如果您已经在使用Spring框架,可以方便地集成Spring Retry,但可能需要更多的Spring依赖。

猜你喜欢

转载自blog.csdn.net/weixin_43820024/article/details/132215206