Bootstrap

Spring Retry方法重试


介绍

Spring Retry是Spring框架提供的用于处理重试操作的模块。它旨在简化在应用程序中处理失败和异常情况的重试逻辑。

使用

  1. 引入依赖

    <dependency>
        <groupId>org.springframework.retry</groupId>
        <artifactId>spring-retry</artifactId>
        <version>1.3.1</version>
    </dependency>
    
  2. 启用重试

    在Spring配置类上添加@EnableRetry注解,启用重试功能。

  3. 使用重试

    • @Retryable注解方式
    • RetryTemplate模板方式

注解方式:@Retryable

当使用@Retryable注解的方法在执行时发生异常时,Spring将自动重试该方法,直到方法成功执行或达到最大重试次数。

Retryable注解的属性

  • interceptor

    说明:该属性用于指定一个重试拦截器。重试拦截器可以在每次重试之前或之后执行特定操作,例如记录日志、执行某些额外的逻辑等。通过指定拦截器,可以对重试过程进行更精细的控制和扩展。

    注意:interceptor属性与其它属性是互斥的。

    用途:通常用于实现自定义的重试逻辑、日志记录、性能统计等。

  • value

    说明:该属性用于指定触发重试的异常类型。它是一个Class类型的数组,可以指定一个或多个异常类。只有当方法抛出指定类型的异常时,才会触发重试机制。

    注意:value与include类型类似,优先级高于include。默认为空(如果exclude排除也为空,则重试所有异常)

    用途:用于定义哪些异常应该触发重试策略,可以根据具体业务需求和异常类型进行设置。

  • include

    说明:该属性用于指定必须包含在内的异常类型,只有这些异常会触发重试。与value属性相似,但优先级低于value。如果同时指定了include和value,则以value为准。

    注意:include与value类型类似,优先级低于include。默认为空(如果exclude排除也为空,则重试所有异常)

    用途:在某些情况下,你可能只想对特定的异常进行重试,而不是对所有异常都进行重试。

  • exceptionExpression

    说明:该属性是一个SpEL(Spring Expression Language)表达式,用于进一步定义哪些异常应该触发重试。它允许根据异常的详细信息进行更灵活的控制。

    用途:用于根据自定义表达式来决定是否进行重试,例如根据异常的特定属性、消息内容等。

  • exclude

    说明:该属性用于指定需要排除的异常类型,即使它们匹配了value、include或exceptionExpression指定的条件,也不会触发重试。

    注意:如果include为空但exclude不为空,则重试所有未排除的异常。

    用途:用于明确排除某些异常,确保它们不会触发重试机制。

  • label

    说明:为重试策略提供一个标签。这个标签可以用于日志、监控或其他目的,以区分不同的重试策略。

    用途:帮助在多个重试策略中进行标识和区分。

  • stateful

    说明:表示该重试是有状态的还是无状态的。默认为false(无状态)。在有状态重试中,每次尝试可能会受之前尝试的影响。

    用途:适用于那些前一次尝试的结果会影响下一次尝试的场景。

  • maxAttempts

    说明:该属性用于设置最大重试次数(包括第1次调用)。它表示在方法连续失败的情况下,最多可以尝试多少次重试。当达到最大重试次数后,如果方法仍然失败,将不再继续重试。

    用途:用于控制重试的次数,避免无限循环重试导致的资源浪费和性能问题。

  • maxAttemptsExpression

    说明:与maxAttempts功能类似,但它接受一个SpEL表达式,用于动态地计算最大重试次数。

    用途:为最大重试次数提供动态配置能力,根据实际运行时的参数、环境变量等来决定最大重试次数。

  • backoff

    说明:该属性用于配置退避策略,即两次连续重试之间的延迟策略等。常见的策略包括固定延迟和指数延迟。属性指定一个@Backoff注解

    Backoff注解的属性:

    • value:用于指定重试间隔的起始值(默认为 1000 毫秒)。
    • delay:用于指定固定的重试间隔(默认为 0)。
    • maxDelay:用于指定重试间隔的最大值(默认为 0,表示没有最大间隔限制)。
    • multiplier:用于计算下一个重试间隔的倍数(默认为 0,表示不使用指数级增长)。
    • maxDelayExpression:用于指定重试间隔的最大表达式,允许你使用 SpEL 表达式动态地计算最大的重试间隔。
    • multiplierExpression:用于指定重试间隔的倍数表达式,允许你使用 SpEL 表达式动态地计算重试间隔的倍数。
    • random:用于指定是否使用随机间隔时间(默认为 false)。

    用途:通过设置合适的退避策略,可以避免频繁的重试对系统造成过大压力,同时合理分散请求,提高系统的可用性和稳定性。

  • listeners

    说明:定义重试事件的监听器。这些监听器可以捕获到重试的开始、结束、失败等事件,并据此执行相应的操作。

    用途:用于实现自定义的重试事件处理逻辑,如发送通知、记录详细日志等。

Retryable注解的使用

  1. 定义重试方法:在您希望进行重试的方法上添加@Retryable注解。

    @Retryable注解的属性:

    • value:指定需要重试的异常类型。可以是单个异常类型或异常类型数组。
    • maxAttempts:指定最大重试次数。
    • backoff:指定重试间隔和退避策略。
  2. 定义重试失败处理方法:如果达到最大重试次数但仍然失败,可以使用@Recover注解在另一个方法中定义重试失败的处理逻辑。

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

    @Autowired
    private RetryTest retryTest;

    @GetMapping(value = "testAnnotation")
    public ApiResult test() {
        retryTest.test();
        return ApiUtil.success();
    }
}
@EnableRetry
@Component
public class RetryTest {

    @Retryable(
            value = {Exception.class},                          // 指定需要重试的异常类型
            maxAttempts = 3,                                    // 最大重试次数
            backoff = @Backoff(delay = 1000, multiplier = 2)    // 重试间隔和退避策略
    )
    public void test() {
        System.out.println("RetryTest.test() 调用"+ DateUtil.formatDateTime(new Date()));
        int i = 1 / 0;
    }

    @Recover
    public void recover(Exception e) {
        System.out.println("RetryTest.test() 重试仍然失败了");
        System.out.println(e);
    }
}

日志

RetryTest.test() 调用2023-11-13 22:17:03
RetryTest.test() 调用2023-11-13 22:17:04
RetryTest.test() 调用2023-11-13 22:17:06
RetryTest.test() 重试仍然失败了
java.lang.ArithmeticException: / by zero

模板方式:RetryTemplate

使用RetryTemplate模板实现重试。

@Configuration
public class RetryTemplateConfig {

    @Bean
    public RetryTemplate retryTemplate() {
        RetryTemplate retryTemplate = new RetryTemplate();

        // 定义重试策略
        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
        retryPolicy.setMaxAttempts(3); // 最大重试次数
        retryTemplate.setRetryPolicy(retryPolicy);

        // 定义重试间隔
        ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
        backOffPolicy.setInitialInterval(1000);
        backOffPolicy.setMultiplier(2);
        retryTemplate.setBackOffPolicy(backOffPolicy);

        return retryTemplate;
    }
}
@RestController
@RequestMapping(value = "/retry")
public class RetryController {

    @Autowired
    private RetryTest retryTest;
    @Autowired
    private RetryTemplate retryTemplate;

    @GetMapping(value = "testAnnotation")
    public ApiResult test() {
        retryTest.test();
        return ApiUtil.success();
    }

    @GetMapping(value = "testTemplate")
    public ApiResult test1() {
        retryTemplate.execute(
                retryContext -> {
                    // 业务逻辑
                    System.out.println("RetryTest.test() 调用"+ DateUtil.formatDateTime(new Date()));
                    int i = 1 / 0;
                    return null;
                    },
                retryContext -> {
                    System.out.println("RetryTest.test() 重试仍然失败了");
                    System.out.println(retryContext.getLastThrowable());
                    return null;
                });
        return ApiUtil.success();
    }
}

日志

RetryTest.test() 调用2023-11-13 22:24:05
RetryTest.test() 调用2023-11-13 22:24:06
RetryTest.test() 调用2023-11-13 22:24:08
RetryTest.test() 重试仍然失败了
java.lang.ArithmeticException: / by zero
;