欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring Boot 整合——Spring retry的基本使用

程序员文章站 2022-05-01 16:58:13
...

关于版本

依赖 版本
springboot 2.4.0
spring retry 2.4.0

代码地址

因为每个例子涉及代码较多,且包含测试用例,如果都贴到文章中内容过多,所以只贴出了部分代码。全部的代码在这里:https://gitee.com/daifyutils/springboot-samples。

相关文章

Spring Boot 整合——Spring retry有状态重试以及其注释和组件介绍

Spring retry

之前在介绍Spring batch的提到过其重试功能之后被独立出了一个新的项目spring retry。通常来说我们可以使用retry来解决一些因为网络波动而导致的响应问题。

retry的原理

在retry的启动注解中可以看到里面的参数,其本质是通过AOP来实现对重试方法的切入然后进行代理以实现方法重试

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@EnableAspectJAutoProxy(proxyTargetClass = false)
@Import(RetryConfiguration.class)
@Documented
public @interface EnableRetry {

	/**
	 * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed to
	 * standard Java interface-based proxies. The default is {@code false}.
	 * @return whether to proxy or not to proxy the class
	 */
	boolean proxyTargetClass() default false;

}

设计实现

要想对业务进行重试,需要定义相关内容:

  1. 需要重试的逻辑(重试方法)
  2. 遇见何种异常进行重试以及重试的限制(重试策略)
  3. 两次重试之间的间隔(退避策略)

重试方法

重试的逻辑就是我们实际上进行业务的内容,这里需要我们自己定义

重试策略

Spring retry通过接口RetryPolicy定义了重试的策略。

public interface RetryPolicy extends Serializable {

	/**
	 * 是否可以进行重试
	 */
	boolean canRetry(RetryContext context);

	/**
	 * 重试上下文
	 */
	RetryContext open(RetryContext parent);

	/**
	 * 清理此次重试环境
	 */
	void close(RetryContext context);

	/**
	 * 重试异常的注册
	 */
	void registerThrowable(RetryContext context, Throwable throwable);

}

通过JAVA代码完成对重试策略的定义

    /**
     * 固定次数重试策略,默认重试最大次数为3次,RetryTemplate默认使用的策略
     * @return
     */
    public static RetryPolicy getSimpleRetryPolicy() {
        SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
        simpleRetryPolicy.setMaxAttempts(5);
        return simpleRetryPolicy;
    }

这些参数使用注解去配置时其参数配置在@Retryable

@Retryable(
            value = Exception.class, // 指定异常进行充数
            include = {},// 处理异常
            exclude = {},// 例外异常
            maxAttempts = 5 // 最大重试次数
    )

退避策略

Spring retry通过接口BackOffPolicy定义了重试的策略。

public interface BackOffPolicy {

	/**
	 * 开启退避策略
	 */
	BackOffContext start(RetryContext context);

	/**
	 * 退避逻辑的实现
	 */
	void backOff(BackOffContext backOffContext) throws BackOffInterruptedException;

}

关于如何实现重试之间的停顿可以看其中实现类FixedBackOffPolicy的代码,通过Sleeper来实现停顿

	public FixedBackOffPolicy withSleeper(Sleeper sleeper) {
		FixedBackOffPolicy res = new FixedBackOffPolicy();
		res.setBackOffPeriod(backOffPeriod);
		res.setSleeper(sleeper);
		return res;
	}

通过JAVA代码完成对退避策略的定义

    /**
     * 设置固定退避策略
     * @return
     */
    public static BackOffPolicy getFixedBackOffPolicy() {
        FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
        // 设置固定的延迟时间
        fixedBackOffPolicy.setBackOffPeriod(2000L);
        return fixedBackOffPolicy;
    }

这些参数使用注解去配置时其参数配置在@Backoff

    @Retryable(
            ....
            backoff = @Backoff( // 重试等待策略
                    delay = 2000L,// 重试间隔
                    multiplier = 1.5// 多次重试间隔系数2 、3、4.5
                    )
    )

快速启动

下面是介绍如何快速配置一个重试demo

添加依赖

无论使用注解方式还是代码方式进行重试逻辑都需要引入依赖

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

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>
    </dependencies>

注解方式进行重试

一般说推荐使用注解的方式

启动类添加注解

@SpringBootApplication
// 开启重试的支持
@EnableRetry
public class RetryApplication {

    public static void main(String[] args) {
        SpringApplication.run(RetryApplication.class,args);
    }

}

为需要重试的方法添加注解

@Service
public class RetryService {

    private Logger logger = LoggerFactory.getLogger(getClass());

    private final int totalNum = 100000;

    /**
     * 测试业务支持重试
     * @param num
     * @return
     * @throws Exception
     */
    @Retryable(
            value = Exception.class, // 指定异常进行充数
            include = {},// 处理异常
            exclude = {},// 例外异常
            maxAttempts = 3, // 最大重试次数
            backoff = @Backoff( // 重试等待策略
                    delay = 2000L,// 重试间隔
                    multiplier = 1.5// 多次重试间隔系数2 、3、4.5
                    )
    )
    public int baseRetry(int num) throws Exception {
        logger.info("开始执行业务" + LocalTime.now());
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            logger.error("illegal");
        }
        if (num <= 0) {
            throw new IllegalArgumentException("数量不对");
        }
        logger.info("业务执行结束" + LocalTime.now());
        return totalNum - num;
    }

    /**
     * 重试失败后的兜底方法
     * @param e
     * @return
     */
    @Recover
    public int baseRetryRecover(Exception e) {
        logger.warn("业务执行失败!!!" + LocalTime.now());
        return totalNum;
    }
}

注解方式进行重启的注解介绍

重试注解

重试注解 注解说明
@EnableRetry 开启重试,proxyTargetClass属性为true时(默认false),使用CGLIB代理
@Retryable 注解需要被重试的方法
@Backoff 重试回退策略,(立即重试还是等待一会再重试)
@Recover 用于方法,用于@Retryable失败时的“兜底”处理方法
@CircuitBreaker 用于方法,实现熔断模式

使用注解进行重试时需要注意

  1. 在同一个类中方法调用重试方法无效

  2. 静态方法无效

  3. @Recover方法返回值类型要和重试方法返回值类型相同

PS. 上面三个错误的情况在源代码中都已经列出来了。这里就不贴相关内容了。

使用代码方式进行重试

启动类中无需添加注解

如果不使用注解方式启用重试则可以不需要@EnableRetry注解

创建退避策略

/**
 * 退避策略
 * @author daify
 * @date 2021-01-04
 */
public class RetryBackOffUtils {


    /**
     * 设置固定退避策略
     * @return
     */
    public static BackOffPolicy getFixedBackOffPolicy() {
        FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
        // 设置固定的延迟时间
        fixedBackOffPolicy.setBackOffPeriod(2000L);
        return fixedBackOffPolicy;
    }

}

创建重试策略

/**
 * 重试策略
 */
public class RetryPolicyUtils {

    /**
     * 固定次数重试策略,默认重试最大次数为3次,RetryTemplate默认使用的策略
     * @return
     */
    public static RetryPolicy getSimpleRetryPolicy() {
        SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
        simpleRetryPolicy.setMaxAttempts(5);
        return simpleRetryPolicy;
    }

}

使用RetryTemplate来进行重试

@Service
public class RetryPolicyService {

    private Logger logger = LoggerFactory.getLogger(getClass());

    private int totalNum = 3;

    /**
     * 使用RetryTemplate进行重试
     * @param num
     * @return
     * @throws Throwable
     */
    public int baseRetryTemplate(int num) throws Throwable {
        RetryTemplate template = RetryTemplate.builder().retryOn(Exception.class).build();
        // 设置退避策略
        template.setBackOffPolicy(RetryBackOffUtils.getFixedBackOffPolicy());
        // 设置重试策略
        template.setRetryPolicy(RetryPolicyUtils.getSimpleRetryPolicy());
        // 设置执行方法
        template.execute(new RetryCallback<Object, Throwable>() {
            @Override
            public Object doWithRetry(RetryContext context) throws Throwable {
                return baseRetry(num);
            }
        }, new RecoveryCallback() {
            @Override
            public Object recover(RetryContext context) throws Exception {
                baseRetryRecover();
                return totalNum;
            }
        });
        return totalNum - num;
    }

    /**
     * 执行业务
     * @param num
     * @return
     * @throws Exception
     */
    public int baseRetry(Integer num) throws Exception {
        logger.info("baseRetry开始执行业务" + LocalTime.now());
        totalNum = totalNum + num;
        if (totalNum >= 0) {
            logger.error("执行任务失败,数据为:{}",totalNum);
            throw new IllegalArgumentException("执行任务失败,数据为:" + totalNum);
        }
        logger.info("baseRetry业务执行结束" + LocalTime.now());
        return totalNum - num;
    }

    /**
     * 重试失败后的兜底方法
     * @return
     */
    public int baseRetryRecover() {
        logger.warn("业务执行失败!!!" + LocalTime.now());
        return totalNum;
    }

}

个人水平有限,上面的内容可能存在没有描述清楚或者错误的地方,请及时告知,我会第一时间修改相关内容,也希望大家看在这个新春佳节只能宅到家中埋头苦逼的码代码的情况下,能给我点一个赞。你的点赞就是我前进的动力。