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

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

程序员文章站 2022-05-01 17:06:02
...

关于版本

依赖 版本
springboot 2.4.0
spring retry 2.4.0

代码地址

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

相关文章

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

通过配置状态重试来使用CircuitBreaker带熔断功能的重试

无状态的重试

无状态重试,是在一个循环中执行完重试策略,即重试上下文保持在一个线程上下文中。

有状态的重试

有状态的重试时,上下文会保存在一个公共的缓存中。

  1. 注释中设置有状态的重试
@Retryable(stateful = true)
  1. JAVA中使用DefaultRetryState保存状态
    Object key = "retryState";
    boolean isForceRefresh = false;
    RetryState state = new DefaultRetryState(key, isForceRefresh);

带熔断功能的重试

使用CircuitBreaker我们可以实现熔断功能的重试。其需要设置三个参数

CircuitBreakerRetryPolicy retryPolicy =
        new CircuitBreakerRetryPolicy(new SimpleRetryPolicy(5));
// 此参数用来判断熔断器是否打开的超时时间
retryPolicy.setOpenTimeout(3000L);
// 此参数用来判断熔断器是否重置的超时时间
retryPolicy.setResetTimeout(9000L);

熔断逻辑的判断

其两个参数主要的应用是在判断熔断器电路是否打开的逻辑CircuitBreakerRetryPolicyisOpen

		public boolean isOpen() {
			long time = System.currentTimeMillis() - this.start;
			boolean retryable = this.policy.canRetry(this.context);
			if (!retryable) {// 如果不能重试
			    // 如果上次请求时间大于this.timeout 重置上下文
				if (time > this.timeout) {
					logger.trace("Closing");
					this.context = createDelegateContext(policy, getParent());
					this.start = System.currentTimeMillis();
					retryable = this.policy.canRetry(this.context);
				}
				else if (time < this.openWindow) {
				// 如果上次请求小于this.openWindow,则打开断路器
					if ((Boolean) getAttribute(CIRCUIT_OPEN) == false) {
						logger.trace("Opening circuit");
						setAttribute(CIRCUIT_OPEN, true);
					}
					this.start = System.currentTimeMillis();
					return true;
				}
			}
			else {// 可以进行重试
			// 如果距离上次请求时间大于openWindow则断路器闭合,重置上下文
				if (time > this.openWindow) {
					logger.trace("Resetting context");
					this.start = System.currentTimeMillis();
					this.context = createDelegateContext(policy, getParent());
				}
			}
			if (logger.isTraceEnabled()) {
				logger.trace("Open: " + !retryable);
			}
			setAttribute(CIRCUIT_OPEN, !retryable);
			return !retryable;
		}

上面的逻辑可以得到结论,总结下来。

this.timeout 为设置的 ResetTimeout (9000L)

this.openWindow 为设置的 OpenTimeout (3000L)

this.policy.canRetry(this.context) == false重试失败。

  1. 如果两次请求时间大于ResetTimeout则重置上下文后,重新判断canRetry

  2. 如果两次请求时间小于OpenTimeout则打开断路器

this.policy.canRetry(this.context) == true可以继续进行重试。

  1. 如果两次请求时间大于OpenTimeout则重置上下文

带熔断功能重试的例子

JAVA

    public void retryState(long sleepTime){
        RetryTemplate template = RetryTemplate.builder().retryOn(Exception.class).build();
        CircuitBreakerRetryPolicy retryPolicy =
                new CircuitBreakerRetryPolicy(new SimpleRetryPolicy(5));
        // 此参数用来判断熔断器是否打开的超时时间动
        retryPolicy.setOpenTimeout(2000L);
        // 此参数用来判断熔断器是否重置的超时时间
        retryPolicy.setResetTimeout(20000L);
        template.setRetryPolicy(retryPolicy);
        Object key = "retryState";
        boolean isForceRefresh = false;
        RetryState state = new DefaultRetryState(key, isForceRefresh);
        for (int i = 0; i < 20; i++) {
            try {
                String result = template.execute(new RetryCallback<String, RuntimeException>() {
                    @Override
                    public String doWithRetry(RetryContext context) throws RuntimeException {
                        log.info("重试次数:" + context.getRetryCount());
                        log.warn("业务执行失败!!!" + LocalTime.now());
                        try {
                            Thread.sleep(200L * 1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        throw new RuntimeException("timeout");
                    }
                }, new RecoveryCallback<String>() {
                    @Override
                    public String recover(RetryContext context) throws Exception {
                        log.warn("业务执行recover!!!" + LocalTime.now());
                        return "default";
                    }
                },state);
                if (result.equals("default")) {
                    try {
                        log.info("请求被拒绝,休眠{}毫秒后再次请求。",sleepTime);
                        Thread.sleep(sleepTime);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } catch (Exception e) {
                System.out.println(e);
            }
        }
    }

使用注解

    @CircuitBreaker(
            openTimeout = 3000L,
            resetTimeout = 7000L,
            maxAttempts = 5 // 最大重试次数
    )
    public String retryAnnotationState(){
        log.warn("业务执行失败!!!" + LocalTime.now());
        throw new RuntimeException("timeout");
    }

    @Recover
    public String recover() {
        log.info("请求被拒绝。");
        return "default";
    }

测试用例

以JAVA代码中的demo为测试例子

    /**
     * 有状态的重试,此时5次重试后熔断,重试时间小于openTimeout
     * @throws Exception
     */
    @Test
    public void baseRetry() throws Exception {
        retryStateService.retryState(1000L * 1);
    }

在熔断上下文被重置,然后使用小于OpenTimeout的频率发送请求,如此断路器会被持续打开,且上下文无法重置,请求会被持续性的熔断。

2021-01-17 21:43:43.711  INFO 17848 --- [           main] d.s.retry.service.RetryStateService      : 请求被拒绝,休眠1500毫秒后再次请求。
2021-01-17 21:43:45.211  WARN 17848 --- [           main] d.s.retry.service.RetryStateService      : 业务执行recover!!!21:43:45.211
2021-01-17 21:43:45.211  INFO 17848 --- [           main] d.s.retry.service.RetryStateService      : 请求被拒绝,休眠1500毫秒后再次请求。
2021-01-17 21:43:46.711  WARN 17848 --- [           main] d.s.retry.service.RetryStateService      : 业务执行recover!!!21:43:46.711
2021-01-17 21:43:46.711  INFO 17848 --- [           main] d.s.retry.service.RetryStateService      : 请求被拒绝,休眠1500毫秒后再次请求。
2021-01-17 21:43:48.212  WARN 17848 --- [           main] d.s.retry.service.RetryStateService      : 业务执行recover!!!21:43:48.212
2021-01-17 21:43:48.212  INFO 17848 --- [           main] d.s.retry.service.RetryStateService      : 请求被拒绝,休眠1500毫秒后再次请求。
2021-01-17 21:43:49.712  WARN 17848 --- [           main] d.s.retry.service.RetryStateService      : 业务执行recover!!!21:43:49.712
2021-01-17 21:43:49.712  INFO 17848 --- [           main] d.s.retry.service.RetryStateService      : 请求被拒绝,休眠1500毫秒后再次请求。
2021-01-17 21:43:51.212  WARN 17848 --- [           main] d.s.retry.service.RetryStateService      : 业务执行recover!!!21:43:51.212
2021-01-17 21:43:51.212  INFO 17848 --- [           main] d.s.retry.service.RetryStateService      : 请求被拒绝,休眠1500毫秒后再次请求。
2021-01-17 21:43:52.713  WARN 17848 --- [           main] d.s.retry.service.RetryStateService      : 业务执行recover!!!21:43:52.713
2021-01-17 21:43:52.713  INFO 17848 --- [           main] d.s.retry.service.RetryStateService      : 请求被拒绝,休眠1500毫秒后再次请求。

同样以JAVA中代码为测试例子

    /**
     * 有状态的重试,后熔断,重试总时间大于ResetTimeout
     * @throws Exception
     */
    @Test
    public void baseRetry2() throws Exception {
        retryStateService.retryState(2100L);
    }

在熔断上下文被重置,然后使用大于OpenTimeout的频率发送请求,则在时间到达ResetTimeout之后可以重新进行重试

2021-01-17 21:45:46.768  INFO 22244 --- [           main] d.s.retry.service.RetryStateService      : 重试次数:4
2021-01-17 21:45:46.768  WARN 22244 --- [           main] d.s.retry.service.RetryStateService      : 业务执行失败!!!21:45:46.768
java.lang.RuntimeException: timeout
2021-01-17 21:45:46.969  WARN 22244 --- [           main] d.s.retry.service.RetryStateService      : 业务执行recover!!!21:45:46.969
2021-01-17 21:45:46.969  INFO 22244 --- [           main] d.s.retry.service.RetryStateService      : 请求被拒绝,休眠2100毫秒后再次请求。
2021-01-17 21:45:49.070  WARN 22244 --- [           main] d.s.retry.service.RetryStateService      : 业务执行recover!!!21:45:49.070
2021-01-17 21:45:49.070  INFO 22244 --- [           main] d.s.retry.service.RetryStateService      : 请求被拒绝,休眠2100毫秒后再次请求。
2021-01-17 21:45:51.170  WARN 22244 --- [           main] d.s.retry.service.RetryStateService      : 业务执行recover!!!21:45:51.170
2021-01-17 21:45:51.170  INFO 22244 --- [           main] d.s.retry.service.RetryStateService      : 请求被拒绝,休眠2100毫秒后再次请求。
2021-01-17 21:45:53.271  WARN 22244 --- [           main] d.s.retry.service.RetryStateService      : 业务执行recover!!!21:45:53.271
2021-01-17 21:45:53.271  INFO 22244 --- [           main] d.s.retry.service.RetryStateService      : 请求被拒绝,休眠2100毫秒后再次请求。
2021-01-17 21:45:55.371  WARN 22244 --- [           main] d.s.retry.service.RetryStateService      : 业务执行recover!!!21:45:55.371
2021-01-17 21:45:55.371  INFO 22244 --- [           main] d.s.retry.service.RetryStateService      : 请求被拒绝,休眠2100毫秒后再次请求。
2021-01-17 21:45:57.471  WARN 22244 --- [           main] d.s.retry.service.RetryStateService      : 业务执行recover!!!21:45:57.471
2021-01-17 21:45:57.471  INFO 22244 --- [           main] d.s.retry.service.RetryStateService      : 请求被拒绝,休眠2100毫秒后再次请求。
2021-01-17 21:45:59.572  WARN 22244 --- [           main] d.s.retry.service.RetryStateService      : 业务执行recover!!!21:45:59.572
2021-01-17 21:45:59.572  INFO 22244 --- [           main] d.s.retry.service.RetryStateService      : 请求被拒绝,休眠2100毫秒后再次请求。
2021-01-17 21:46:01.672  WARN 22244 --- [           main] d.s.retry.service.RetryStateService      : 业务执行recover!!!21:46:01.672
2021-01-17 21:46:01.672  INFO 22244 --- [           main] d.s.retry.service.RetryStateService      : 请求被拒绝,休眠2100毫秒后再次请求。
2021-01-17 21:46:03.773  WARN 22244 --- [           main] d.s.retry.service.RetryStateService      : 业务执行recover!!!21:46:03.773
2021-01-17 21:46:03.773  INFO 22244 --- [           main] d.s.retry.service.RetryStateService      : 请求被拒绝,休眠2100毫秒后再次请求。
2021-01-17 21:46:05.873  WARN 22244 --- [           main] d.s.retry.service.RetryStateService      : 业务执行recover!!!21:46:05.873
2021-01-17 21:46:05.874  INFO 22244 --- [           main] d.s.retry.service.RetryStateService      : 请求被拒绝,休眠2100毫秒后再次请求。
2021-01-17 21:46:07.975  INFO 22244 --- [           main] d.s.retry.service.RetryStateService      : 重试次数:0
2021-01-17 21:46:07.975  WARN 22244 --- [           main] d.s.retry.service.RetryStateService      : 业务执行失败!!!21:46:07.975
java.lang.RuntimeException: timeout
2021-01-17 21:46:08.175  INFO 22244 --- [           main] d.s.retry.service.RetryStateService      : 重试次数:1
2021-01-17 21:46:08.175  WARN 22244 --- [           main] d.s.retry.service.RetryStateService      : 业务执行失败!!!21:46:08.175

使用注释和RetryTemplate的区别

这是一个没有解决的问题,有清楚怎么配置的同学可以告知下

使用RetryTemplate进行重试的时候,其会进行多次重试后达到最大重试次数后进入熔断时进入Recover方法。

而使用@CircuitBreaker进行熔断重试的时候,其进行多次重试时每次失败都会进入Recover方法然后熔断后才会只执行Recover方法。

重试注解介绍

用来定义重试策略的注解

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retryable {

	// 设置兜底方法的方法名,默认为空获取当前类中Recover注解的方法
	String recover() default "";

	// 配置重试监听器bean名称
	String interceptor() default "";

	// 用来设置需要进行重试的异常
	Class<? extends Throwable>[] value() default {};

	// 等价于value一样是来确定需要进行重试的异常
	Class<? extends Throwable>[] include() default {};

	// 设置例外处理,例外的异常不需要处理
	Class<? extends Throwable>[] exclude() default {};

	// 定义重试统计的标签
	String label() default "";

	// 标识重试有状态的
	boolean stateful() default false;

	// 最大的重试次数
	int maxAttempts() default 3;

	// 返回一个重试最大次数的公式
	String maxAttemptsExpression() default "";

	// 为两次重试指定的Backoff属性
	Backoff backoff() default @Backoff();

	// 根据异常调用不同处理逻辑的公式
	String exceptionExpression() default "";
	// 重试接听器
	String[] listeners() default {};

}

用来定义兜底方法的注解

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Import(RetryConfiguration.class)
@Documented
public @interface Recover {

}

用来在入口类中定义是否启动重试功能

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

	// 当proxyTargetClass属性为true时,使用CGLIB代理。默认使用基于标准Java接口的代理
	boolean proxyTargetClass() default false;

}

提供熔断功能的重试

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Retryable(stateful = true)
public @interface CircuitBreaker {

	// 
	Class<? extends Throwable>[] value() default {};

	// 
	Class<? extends Throwable>[] include() default {};

	// 
	Class<? extends Throwable>[] exclude() default {};

	// 
	int maxAttempts() default 3;

	// 
	String maxAttemptsExpression() default "";

	// 
	String label() default "";

	// 配置重置熔断器重新闭合的超时时间
	long resetTimeout() default 20000;

	// 熔断器重新闭合的超时时间的文字公式
	String resetTimeoutExpression() default "";

	// 配置熔断器电路打开的超时时间
	long openTimeout() default 5000;

	// 熔断器电路打开的超时时间的文字公式
	String openTimeoutExpression() default "";

	// 
	String exceptionExpression() default "";

}

退避策略

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Backoff {

	// 等同于delay,确定延迟时间
	long value() default 1000;

	// 指定重试的延迟时间
	long delay() default 0;

	// 最长的延迟时间
	long maxDelay() default 0;

	// 指定延迟的倍数
	double multiplier() default 0;

	// 延迟时间的函数公式
	String delayExpression() default "";

	// 最长延迟使劲的函数公式
	String maxDelayExpression() default "";

	// 延迟倍数的函数公式
	String multiplierExpression() default "";

	// 在multiplier设置的情况下设置该值为true将使再重试延迟随机化,
	boolean random() default false;

}

重试JAVA代码介绍

重试策略

重试实现类 说明
NeverRetryPolicy 只允许调用RetryCallback一次,不允许重试
AlwaysRetryPolicy 允许无限重试,直到成功,此方式逻辑不当会导致死循环
SimpleRetryPolicy 固定次数重试策略,默认重试最大次数为3次,RetryTemplate默认使用的策略
TimeoutRetryPolicy 超时时间重试策略,默认超时时间为1秒,在指定的超时时间内允许重试
ExceptionClassifierRetryPolicy 设置不同异常的重试策略,类似组合重试策略,区别在于这里只区分不同异常的重试
CircuitBreakerRetryPolicy 有熔断功能的重试策略
CompositeRetryPolicy 组合重试策略,有两种组合方式,乐观组合重试策略是指只要有一个策略允许重试即可以,悲观组合重试策略是指只要有一个策略不允许重试即可以

NeverRetryPolicy

    /**
     * 只允许调用RetryCallback一次,不允许重试
     * @return
     */
    public static RetryPolicy getNeverRetryPolicy() {
        NeverRetryPolicy neverRetryPolicy = new NeverRetryPolicy();
        return neverRetryPolicy;
    }

AlwaysRetryPolicy

    /**
     * 允许无限重试,直到成功,此方式逻辑不当会导致死循环
     * @return
     */
    public static RetryPolicy getAlwaysRetryPolicy() {
        AlwaysRetryPolicy alwaysRetryPolicy = new AlwaysRetryPolicy();
        return alwaysRetryPolicy;
    }

SimpleRetryPolicy

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

TimeoutRetryPolicy

    /**
     * 超时时间重试策略,默认超时时间为1秒,在指定的超时时间内允许重试
     * @return
     */
    public static RetryPolicy getTimeoutRetryPolicy() {
        TimeoutRetryPolicy timeoutRetryPolicy = new TimeoutRetryPolicy();
        timeoutRetryPolicy.setTimeout(3000L);
        return timeoutRetryPolicy;
    }

ExceptionClassifierRetryPolicy

    /**
     * 设置不同异常的重试策略,类似组合重试策略,区别在于这里只区分不同异常的重试
     * @return
     */
    public static RetryPolicy getExceptionClassifierRetryPolicy() {
        ExceptionClassifierRetryPolicy exceptionClassifierRetryPolicy = new ExceptionClassifierRetryPolicy();
        Map map = new HashMap();
        map.put(RuntimeException.class,getNeverRetryPolicy());
        map.put(Exception.class,getSimpleRetryPolicy());
        exceptionClassifierRetryPolicy.setPolicyMap(map);
        return exceptionClassifierRetryPolicy;
    }

CircuitBreakerRetryPolicy

    /**
     * 有熔断功能的重试策略,需设置3个参数openTimeout、resetTimeout和delegate
     * @return
     */
    public static RetryPolicy getCircuitBreakerRetryPolicy() {
        CircuitBreakerRetryPolicy circuitBreakerRetryPolicy = new CircuitBreakerRetryPolicy(new SimpleRetryPolicy());
        circuitBreakerRetryPolicy.setOpenTimeout(5000);
        circuitBreakerRetryPolicy.setResetTimeout(20000);
        return circuitBreakerRetryPolicy;
    }

CompositeRetryPolicy

    /**
     * 组合重试策略,有两种组合方式,乐观组合重试策略是指只要有一个策略允许重试即可以,悲观组合重试策略是指只要有一个策略不允许重试即可以,但不管哪种组合方式,组合中的每一个策略都会执行
     * @return
     */
    public static RetryPolicy getCompositeRetryPolicy() {
        CompositeRetryPolicy compositeRetryPolicy = new CompositeRetryPolicy();
        // 设置是否乐观重试策略
        compositeRetryPolicy.setOptimistic(true);
        compositeRetryPolicy.setPolicies(new RetryPolicy[]{getSimpleRetryPolicy(),getTimeoutRetryPolicy()});
        return compositeRetryPolicy;

    }

退避策略

退避实现类 说明
NoBackOffPolicy 无退避算法策略,每次重试时立即重试
FixedBackOffPolicy 固定时间的退避策略,需设置参数sleeper和backOffPeriod,sleeper指定等待策略,默认是Thread.sleep,即线程休眠,backOffPeriod指定休眠时间,默认1秒
UniformRandomBackOffPolicy 随机时间退避策略,需设置sleeper、minBackOffPeriod和maxBackOffPeriod,该策略在[minBackOffPeriod,maxBackOffPeriod之间取一个随机休眠时间,minBackOffPeriod默认500毫秒,maxBackOffPeriod默认1500毫秒
ExponentialBackOffPolicy 指数退避策略,需设置参数sleeper、initialInterval、maxInterval和multiplier,initialInterval指定初始休眠时间,默认100毫秒,maxInterval指定最大休眠时间,默认30秒,multiplier指定乘数,即下一次休眠时间为当前休眠时间*multiplier
ExponentialRandomBackOffPolicy 随机指数退避策略,引入随机乘数可以实现随机乘数回退

NoBackOffPolicy

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

    /**
     * 无退避策略
     * @return
     */
    public static BackOffPolicy getNoBackOffPolicy() {
        return new NoBackOffPolicy();
    }

FixedBackOffPolicy

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

UniformRandomBackOffPolicy

    /**
     * 设置随机退避策略,设置其最小时间和最大时间
     * @return
     */
    public static BackOffPolicy getUniformRandomBackOffPolicy() {
        UniformRandomBackOffPolicy uniformRandomBackOffPolicy = new UniformRandomBackOffPolicy();
        // 设置最小延迟时间
        uniformRandomBackOffPolicy.setMinBackOffPeriod(500L);
        // 设置最大延迟时间
        uniformRandomBackOffPolicy.setMaxBackOffPeriod(2000L);
        return uniformRandomBackOffPolicy;
    }

ExponentialBackOffPolicy

    public static BackOffPolicy getExponentialBackOffPolicy() {
        ExponentialBackOffPolicy exponentialBackOffPolicy = new ExponentialBackOffPolicy();
        // 设置初始延迟时间
        exponentialBackOffPolicy.setInitialInterval(300L);
        // 设置最大延迟时间
        exponentialBackOffPolicy.setMaxInterval(4000L);
        // 设置两次延迟时间的倍数
        exponentialBackOffPolicy.setMultiplier(3);
        return new NoBackOffPolicy();
    }

ExponentialRandomBackOffPolicy

    /**
     * 指数退避策略
     * @return
     */
    public static BackOffPolicy getExponentialRandomBackOffPolicy() {
        ExponentialRandomBackOffPolicy exponentialRandomBackOffPolicy = new ExponentialRandomBackOffPolicy();
        // 设置初始延迟时间
        exponentialRandomBackOffPolicy.setInitialInterval(300L);
        // 设置最大延迟时间
        exponentialRandomBackOffPolicy.setMaxInterval(4000L);
        // 设置两次延迟时间的倍数
        exponentialRandomBackOffPolicy.setMultiplier(3);
        return new NoBackOffPolicy();
    }