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

  • Post author:
  • Post category:其他



目录


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个参数

    openTimeout



    resetTimeout



    delegate


  • CompositeRetryPolicy:

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

点进 SimpleRetryPolicy 类中看看

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

private volatile BackOffPolicy backOffPolicy = new NoBackOffPolicy();

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


这里以 FixedBackOffPolicy 为例使用


  • NoBackOffPolicy:

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


  • FixedBackOffPolicy:

    固定时间的退避策略,需设置参数

    sleeper



    backOffPeriod



    sleeper

    指定等待策略,默认是

    Thread.sleep

    ,即线程休眠,

    backOffPeriod

    指定休眠时间,默认1秒


  • UniformRandomBackOffPolicy:

    随机时间退避策略,需设置

    sleeper



    minBackOffPeriod



    maxBackOffPeriod

    ,该策略在

    minBackOffPeriod

    ,

    maxBackOffPeriod

    之间取一个随机休眠时间,

    minBackOffPeriod

    默认500毫秒,

    maxBackOffPeriod

    默认1500毫秒


  • ExponentialBackOffPolicy:

    指数退避策略,需设置参数

    sleeper



    initialInterval



    maxInterval



    multiplie

    r,

    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

形式)传递给

Retryer



call

方法。

Retryer

将根据配置的策略执行方法重试,直到达到停止条件或重试成功

总结


Guava-Retry



Spring Retry

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


  1. 框架来源


    • Guava-Retry



      Guava-Retry

      是Google Guava库的一部分,它提供了一种用于重试操作的机制。

    • Spring Retry



      Spring Retry

      是Spring框架的一个模块,专门用于在Spring应用程序中实现重试逻辑。

  2. 库依赖


    • Guava-Retry

      :您需要添加Guava库的依赖来使用

      Guava-Retry


    • Spring Retry

      :您需要添加

      spring-retry

      模块的依赖来使用

      Spring Retry


  3. 配置和注解


    • Guava-Retry

      :重试逻辑通过构建

      Retryer

      实例并定义重试条件、等待策略等来配置。

    • Spring Retry



      Spring Retry

      提供了注解(如

      @Retryable



      @Recover

      等)和编程式配置来实现重试逻辑。

  4. 重试策略


    • Guava-Retry



      Guava-Retry

      允许您基于结果和异常类型来定义重试条件。您可以使用

      RetryerBuilder

      来自定义重试策略。

    • Spring Retry



      Spring Retry

      允许您使用注解来定义重试条件和相关属性,如最大重试次数、重试间隔等。

  5. 等待策略


    • Guava-Retry



      Guava-Retry

      提供了不同的等待策略(如固定等待、指数等待等),您可以选择适合您的应用程序的策略。

    • Spring Retry



      Spring Retry

      允许您通过注解或编程式配置来指定等待时间。

  6. 适用范围


    • Guava-Retry

      :可以用于任何Java应用程序,不仅限于Spring框架。

    • Spring Retry

      :专门设计用于Spring应用程序中,可以与其他Spring功能(如Spring AOP)集成。

  7. 依赖性


    • Guava-Retry

      :相对较轻量级,如果您只需要重试功能,可以考虑使用Guava库的一部分。

    • Spring Retry

      :如果您已经在使用Spring框架,可以方便地集成

      Spring Retry

      ,但可能需要更多的Spring依赖。



版权声明:本文为weixin_43820024原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。