简介

在日常的一些场景中,很多需要进行重试的操作。而spring-retryspring提供的一个基于spring的重试框架,非常简单好用。

Spring应用

导入maven坐标

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

调用类

@Slf4j
public class RetryDemo {

public static boolean retryMethod(Integer param) {
int i = new Random().nextInt(param);
log.info("随机生成的数:{}", i);

if (1 == i) {
log.info("为1,返回true.");
return true;
} else if (i < 1) {
log.info("小于1,抛出参数异常.");
throw new IllegalArgumentException("参数异常");
} else if (i > 1 && i < 10) {
log.info("大于1,小于10,抛出参数异常.");
return false;
} else {
//为其他
log.info("大于10,抛出自定义异常.");
throw new RemoteAccessException("大于10,抛出自定义异常");
}
}
}

测试类

@Slf4j
public class SpringRetryTest {

/**
* 重试间隔时间ms,默认1000ms
*/
private long fixedPeriodTime = 1000L;
/**
* 最大重试次数,默认为3
*/
private int maxRetryTimes = 3;
/**
* 表示哪些异常需要重试
* key一定要为Throwable异常的子类 Class<? extends Throwable>
* value为true表示需要重试
*/
private Map<Class<? extends Throwable>, Boolean> exceptionMap = new HashMap<>();


@Test
public void test() {

// 1 添加异常的处理结果 true为需要重试 false为不需要重试
exceptionMap.put(RemoteAccessException.class, true);

// 2 构建重试模板实例
RetryTemplate retryTemplate = new RetryTemplate();

// 3 设置重试回退操作策略 设置重试间隔时间
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(fixedPeriodTime);

// 4 设置重试策略 设置重试次数 设置异常处理结果
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(maxRetryTimes, exceptionMap);

//5 重试模板添加重试策略 添加回退操作策略
retryTemplate.setRetryPolicy(retryPolicy);
retryTemplate.setBackOffPolicy(backOffPolicy);

// 6 调用方法
Boolean resp = retryTemplate.execute(
// RetryCallback 重试回调方法
retryContext -> {
boolean result = RetryDemo.retryMethod(110);
log.info("方法返回结果= {}", result);
return result;
},
// RecoveryCallback 异常回调方法
retryContext -> {
//
log.info("超过最大重试次数或者抛出了未定义的异常!!!");
return false;
}
);

log.info("接口返回结果 = {}",resp);

}

}

输出结果

[main] INFO com.cf.demo.SpringRetry.SpringRetryTest - 超过最大重试次数或者抛出了未定义的异常!!!
[main] INFO com.cf.demo.SpringRetry.SpringRetryTest - 接口返回结果 = false

从代码的书写注解可以看到,RetryTemplate对象是Spring-Retry框架的重试执行者, 由它添加重试策略回退操作策略等(注释第五步)。RetryTemplate执行重试方法(注释第六步),通过execute方法,传入的参数是重试回调逻辑对象RetryCallback执行操作结束的恢复对象RecoveryCallback。且可以切换添加的异常种类,只有添加过相应的异常,才会触发重试操作,否则直接调用RecoveryCallback对象方法。

源码分析

  • RetryTemplate的部分源码:
/**
* 继续执行回调,直到它成功或策略指示我们停止,在这种情况下将执行恢复回调。
*
* @see RetryOperations#execute(RetryCallback, RecoveryCallback)
* @param retryCallback the {@link RetryCallback}
* @param recoveryCallback the {@link RecoveryCallback}
* @throws TerminatedRetryException if the retry has been manually terminated by a listener.
*/
@Override
public final <T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback,RecoveryCallback<T> recoveryCallback) throws E {
return doExecute(retryCallback, recoveryCallback, null);
}
  • RetryTemplate添加重试策略源码:
/**
* Setter for {@link RetryPolicy}.
*
* @param retryPolicy the {@link RetryPolicy}
*/
public void setRetryPolicy(RetryPolicy retryPolicy) {
this.retryPolicy = retryPolicy;
}
  • RetryPolicy接口实现类
  1. AlwaysRetryPolicy:允许无限重试,直到成功,可能会导致死循环
  2. CircuitBreakerRetryPolicy:有熔断功能的重试策略,需设置3个参数openTimeout(打开超时)、resetTimeout(重置超时)和delegate(委托)
  3. CompositeRetryPolicy组合重试策略,有两种组合方式,乐观组合重试策略是指只要有一个策略允许即可以重试悲观组合重试策略是指只要有一个策略不允许即可以重试;但不管哪种组合方式,组合中的每一个策略都会执行
  4. ExceptionClassifierRetryPolicy:设置不同异常的重试策略,类似组合重试策略,区别在于这里只区分不同异常的重试
  5. NeverRetryPolicy:只允许调用RetryCallback一次,不允许重试
  6. SimpleRetryPolicy:固定次数重试策略,默认重试最大次数为3次,RetryTemplate默认使用的策略`
  7. TimeoutRetryPolicy超时时间重试策略,默认超时时间为1秒,在指定的超时时间内允许重试
  • RetryTemplate添加回退策略源码:
/**
* Setter for {@link BackOffPolicy}.
*
* @param backOffPolicy the {@link BackOffPolicy}
*/
public void setBackOffPolicy(BackOffPolicy backOffPolicy) {
this.backOffPolicy = backOffPolicy;
}
  • BackOffPolicy实现类
  1. ExponentialBackOffPolicy指数退避策略,需设置参数sleeperinitialIntervalmaxIntervalmultiplier;initialInterval指定初始休眠时间,默认100毫秒;maxInterval指定最大休眠时间,默认30秒;multiplier指定乘数,即下一次休眠时间 = 当前休眠时间 * multiplier
  2. ExponentialRandomBackOffPolicy随机指数退避策略,引入随机乘数可以实现随机乘数回退
  3. FixedBackOffPolicy固定时间的退避策略,需设置参数sleeperbackOffPeriod;sleeper指定等待策略,默认是Thread.sleep,即线程休眠;backOffPeriod指定休眠时间,默认1秒
  4. NoBackOffPolicy无退避算法策略,每次重试时立即重试
  5. UniformRandomBackOffPolicy随机时间退避策略,需设置sleeperminBackOffPeriodmaxBackOffPeriod,该策略在[minBackOffPeriod,maxBackOffPeriod]之间取一个随机休眠时间,minBackOffPeriod默认500毫秒,maxBackOffPeriod默认1500毫秒

SpringBoot应用

导入maven坐标

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

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

管理类

@Service
@Slf4j
public class SpringRetryDemo {


/**
* 重试所调用方法
* @return
*/
// delay=2000L表示延迟2秒 multiplier=2表示两倍 即第一次重试2秒后,第二次重试4秒后,第三次重试8秒后
@Retryable(value = {RemoteAccessException.class}, maxAttempts = 3, backoff = @Backoff(delay = 2000L, multiplier = 2))
public boolean call(Integer param) {
return RetryDemo.retryMethod(param);
}

/**
* 超过最大重试次数或抛出没有指定重试的异常
* @param e
* @param param
* @return
*/
@Recover
public boolean recover(Exception e, Integer param) {
log.info("请求参数为: ", param);
log.info("超过最大重试次数或抛出没有指定重试的异常, e = {} ", e.getMessage());
return false;
}
}
  • @Retryable注解,标记的方法发生异常时会重试
  1. value:指定发生的异常进行重试
  2. include:与value一样默认为空,当exclude同时为空时,所有异常都重试
  3. exclude:指定异常不重试,默认为空,当include同时为空,所有异常都重试
  4. maxAttemps:重试次数,默认3
  5. backoff:重试补充机制,默认是@Backoff()注解
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retryable {

/**
* Retry interceptor bean name to be applied for retryable method. Is mutually
* exclusive with other attributes.
* @return the retry interceptor bean name
*/
String interceptor() default "";

/**
* Exception types that are retryable. Synonym for includes(). Defaults to empty (and
* if excludes is also empty all exceptions are retried).
* @return exception types to retry
*/
Class<? extends Throwable>[] value() default {};

/**
* Exception types that are retryable. Defaults to empty (and if excludes is also
* empty all exceptions are retried).
* @return exception types to retry
*/
Class<? extends Throwable>[] include() default {};

/**
* Exception types that are not retryable. Defaults to empty (and if includes is also
* empty all exceptions are retried).
* @return exception types to retry
*/
Class<? extends Throwable>[] exclude() default {};

/**
* A unique label for statistics reporting. If not provided the caller may choose to
* ignore it, or provide a default.
*
* @return the label for the statistics
*/
String label() default "";

/**
* Flag to say that the retry is stateful: i.e. exceptions are re-thrown, but the
* retry policy is applied with the same policy to subsequent invocations with the
* same arguments. If false then retryable exceptions are not re-thrown.
* @return true if retry is stateful, default false
*/
boolean stateful() default false;

/**
* @return the maximum number of attempts (including the first failure), defaults to 3
*/
int maxAttempts() default 3;

/**
* @return an expression evaluated to the maximum number of attempts (including the first failure), defaults to 3
* Overrides {@link #maxAttempts()}.
* @since 1.2
*/
String maxAttemptsExpression() default "";

/**
* Specify the backoff properties for retrying this operation. The default is a
* simple {@link Backoff} specification with no properties - see it's documentation
* for defaults.
* @return a backoff specification
*/
Backoff backoff() default @Backoff();

/**
* Specify an expression to be evaluated after the {@code SimpleRetryPolicy.canRetry()}
* returns true - can be used to conditionally suppress the retry. Only invoked after
* an exception is thrown. The root object for the evaluation is the last {@code Throwable}.
* Other beans in the context can be referenced.
* For example:
* <pre class=code>
* {@code "message.contains('you can retry this')"}.
* </pre>
* and
* <pre class=code>
* {@code "@someBean.shouldRetry(#root)"}.
* </pre>
* @return the expression.
* @since 1.2
*/
String exceptionExpression() default "";
}

当重试耗尽时还是失败,会出现什么情况呢?

当重试耗尽时,RetryOperations可以将控制传递给另一个回调,即RecoveryCallback。Spring-Retry还提供了@Recover注解,用于@Retryable重试失败后处理方法。如果不需要回调方法,可以直接不写回调方法,那么实现的效果是,重试次数完了后,如果还是没成功没符合业务判断,就抛出异常。

  • @Backoff注解
  1. delay:延迟多久后重试
  2. multiplier:延迟的倍数
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(RetryConfiguration.class)
@Documented
public @interface Backoff {

/**
* Synonym for {@link #delay()}.
*
* @return the delay in milliseconds (default 1000)
*/
long value() default 1000;

/**
* A canonical backoff period. Used as an initial value in the exponential case, and
* as a minimum value in the uniform case.
* @return the initial or canonical backoff period in milliseconds (default 1000)
*/
long delay() default 0;

/**
* The maximimum wait (in milliseconds) between retries. If less than the
* {@link #delay()} then the default of
* {@value org.springframework.retry.backoff.ExponentialBackOffPolicy#DEFAULT_MAX_INTERVAL}
* is applied.
*
* @return the maximum delay between retries (default 0 = ignored)
*/
long maxDelay() default 0;

/**
* If positive, then used as a multiplier for generating the next delay for backoff.
*
* @return a multiplier to use to calculate the next backoff delay (default 0 =
* ignored)
*/
double multiplier() default 0;

/**
* An expression evaluating to the canonical backoff period. Used as an initial value
* in the exponential case, and as a minimum value in the uniform case.
* Overrides {@link #delay()}.
* @return the initial or canonical backoff period in milliseconds.
* @since 1.2
*/
String delayExpression() default "";

/**
<<<<<<< HEAD
* An expression evaluating to the maximum wait (in milliseconds) between retries.
* If less than the {@link #delay()} then ignored.
=======
* An expression evaluating to the maximimum wait (in milliseconds) between retries.
* If less than the {@link #delay()} then the default of
* {@value org.springframework.retry.backoff.ExponentialBackOffPolicy#DEFAULT_MAX_INTERVAL}
* is applied.
>>>>>>> Fix @Backoff JavaDocs - maxDelay
* Overrides {@link #maxDelay()}
*
* @return the maximum delay between retries (default 0 = ignored)
* @since 1.2
*/
String maxDelayExpression() default "";

/**
* Evaluates to a vaule used as a multiplier for generating the next delay for backoff.
* Overrides {@link #multiplier()}.
*
* @return a multiplier expression to use to calculate the next backoff delay (default 0 =
* ignored)
* @since 1.2
*/
String multiplierExpression() default "";

/**
* In the exponential case ({@link #multiplier()} &gt; 0) set this to true to have the
* backoff delays randomized, so that the maximum delay is multiplier times the
* previous delay and the distribution is uniform between the two values.
*
* @return the flag to signal randomization is required (default false)
*/
boolean random() default false;

}
  • @Recover注解

当重试达到规定的次数后,被注解标记的方法将被调用,可以在此方法中进行日志的记录等操作。(该方法的入参类型,返回值类型需要和重试方法保持一致)

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

注意

  1. 方法的入参类型、返回值必须与@Retryable方法一致
  2. 方法的第一个参数,必须是Throwable类型的,建议是与@Retryable配置的异常一致
  3. 该回调方法与重试方法写在同一个实现类里面

配置注解

// 启动类上添加注解@EnableRetry
@EnableRetry
@SpringBootApplication
public class DemoApplication {

public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
  • @Enableretry注解,启用重试功能(默认是否基于子类代理(默认”否”), 即是基于Java接口代理)
@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;

}

测试类

@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class)
@Slf4j
public class DemoApplicationTests {

@Autowired
private SpringRetryDemo springRetryDemo;

@Test
public void testRetry() {
boolean result = springRetryDemo.call(110);
log.info("方法返回结果为: {}", result);
}
}
/* 运行结果:

随机生成的数:77
大于10,抛出自定义异常.
随机生成的数:23
大于10,抛出自定义异常.
随机生成的数:82
大于10,抛出自定义异常.
请求参数为:
超过最大重试次数或抛出没有指定重试的异常, e = 大于10,抛出自定义异常
方法返回结果为: false
*/

注意事项

  1. 由于是基于AOP实现,所以不支持类里自调用方法
  2. 如果重试失败需要给@Recover注解的方法做后续处理,那这个重试的方法不能有返回值,只能是void
  3. 方法内不能使用try...catch...,只能往外抛异常
  4. @Recover注解来开启重试失败后调用的方法(需跟重处理方法在同一个类中),此注解注释的方法参数一定要是@Retryable抛出的异常,否则无法识别。