1、概述
spring retry是spring框架的一个模块,它提供了重新调用失败操作的功能。
2、使用场景
远程调用和网络通信:由于网络不稳定或服务不可用,可能会出现连接问题。
数据库交互:执行SQL查询或更新操作时,数据库服务器可能会出现临时性的问题,如死锁或连接丢失。
外部依赖:如果你的应用程序依赖于外部服务、硬件设备或其他不可控因素,而这些依赖可能会偶尔出现故障或不可用状态,那么Spring Retry可以帮助你处理这些情况,确保应用程序在某些情况下能够自动进行重试。
并发控制:在多线程环境中,可能会出现竞争条件或并发问题,导致某些操作失败。
复杂的业务逻辑:某些业务逻辑可能需要多次尝试才能成功
一般情况下,可重试的操作呈现出瞬时性故障,重试有意义。
3、使用方法
maven引入:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.3.4</version>
</dependency>
启用spring retry
增加@EnableRetry到我们的@Configuration类。
@Configuration
@EnableRetry
public class AppConfig { ... }
使用spring retry
1、通过@Retryable进行重试
@Retryable
public void baseUse() {
System.out.println("基础使用。发生于" + System.currentTimeMillis());
throw new RuntimeException("基础使用业务方法出错了");
}
此方法将会执行3次,且执行间隔为1秒钟。
Retryable注解(v1.3.4)详解:
maxAttempts:最大尝试次数(包括第一次失败),默认为 3
maxAttemptsExpression:一个表达式,用于计算最大尝试次数(包括第一次失败),默认为 3,可以覆盖 maxAttempts()。
value:可重试的异常类型。与 includes() 同义。默认为空(如果 excludes 也为空,则会重试所有异常)。
include:同义于value
exclude:不可重试的异常类型。默认为空(如果 includes 也为空,则会重试所有异常)。如果 includes 为空但 excludes 不为空,则会重试所有未被排除的异常。
backoff:指定重试此操作的退避属性。
recover:用于恢复的类中方法的名称。该方法必须标记有 @Recover。
interceptor:应用于可重试方法的拦截器的 bean 名称。配置了它,其他属性将失效。
label:当steteful为true时,标记着为有状态的重试,这个为state的key值。
stateful:表示重试是否具有状态:即异常被重新抛出,但重试策略会应用于具有相同参数的后续调用。如果为 false,则不会重新抛出可重试的异常。
exceptionExpression:指定在 {@code SimpleRetryPolicy.canRetry()} 返回 true 后评估的表达式 - 可用于有条件地抑制重试。仅在抛出异常后调用。用于评估的根对象是最后一个 {@code Throwable}。可以引用上下文中的其他 bean。例如:{@code "message.contains('you can retry this')"}和 {@code"@someBean.shouldRetry(#root)"}
Backoff注解(v1.3.4)详解:
value:延迟时间(毫秒),默认为 1000。当delay不为零时,忽略此元素的值
delay:延迟时间。在指数情况下用作初始值,并在均匀情况下用作最小值。当此元素的值为 0 时,采用value() 的值,否则采用此元素的值。
maxDelay:重试之间的最大等待时间(毫秒)。如果小于 delay(),则应用默认值ExponentialBackOffPolicy. DEFAULT_MAX_INTERVAL(30000)。
multiplier:如果为正数,将用作生成下一个退避延迟的乘数。默认为0,表示忽略。
delayExpression:计算延迟时间的表达式,在指数情况下用作初始值,并在均匀情况下用作最小值。
maxDelayExpression:计算maxDelay的表达式。
multiplierExpression:计算下一个乘数的表达式。
random:当multiplier>0,并且设置该值为true,使退避延迟随机化。以便最大延迟是前一个延迟的 multiplier 倍,分布在两个值之间是均匀的
randomExpression:计算random的表达式。
通常是一个SpEL(Spring Expression Language)表达式,你可以在其中引用方法参数、返回值等。
2、@Recover 注解
定义了一个单独的恢复方法,当 @Retryable 方法因为指定的异常而失败时会调用该恢复方法。
@Recover
public void recover(Throwable throwable) {
System.err.println(Utils.getStackTrace(throwable));
}
// 打印业务异常信息
3、使用RetryTemplate
需要先注入RetryTemplate Bean
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
// 退避策略
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(2000L);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
// 重试策略
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(2);
retryTemplate.setRetryPolicy(retryPolicy);
return retryTemplate;
}
简单调用:
retryTemplate.execute(new RetryCallback<Object, Throwable>() {
@Override
public Object doWithRetry(RetryContext context) throws Throwable {
// 业务逻辑
springRetryService.templateRetry();
return "成功";
}
});
4、原理
核心逻辑流程图
模块:
RetryTemplate(重试模板):RetryTemplate 是 Spring Retry 的核心组件之一,它封装了重试的逻辑。它提供了一组 execute() 方法,允许您执行带有重试逻辑的方法。RetryTemplate 可以配置不同的重试策略、退避策略和监听器。
RetryPolicy(重试策略):RetryPolicy 决定了是否应该重试方法。Spring Retry 提供了不同的 RetryPolicy 实现,例如 SimpleRetryPolicy(固定次数重试)、ExponentialBackOffPolicy(指数退避重试)等。您可以根据需求选择合适的策略。
BackOffPolicy(退避策略):BackOffPolicy 控制重试尝试之间的退避(等待)时间。Spring Retry 提供了不同的 BackOffPolicy 实现,包括 FixedBackOffPolicy(固定等待时间)、ExponentialBackOffPolicy(指数退避)等。
RetryListener(重试监听器):RetryListener 接口定义了在重试期间的回调方法,包括在重试之前、重试之后以及每次不成功的尝试之后。可以在重试过程中进行增强操作。
RetryContext(重试上下文):RetryContext 是用于在重试过程中传递信息的上下文对象。它可以包含有关重试次数、异常信息等的信息。例如:RetryContextSupport包含父类上下文、中止标志、重试次数、上次异常信息。辅助RetryPolicy判断是否应该重试。
BackOffContext(退避上下文):管理和跟踪在重试操作中应用的回退(backoff)策略的上下文信息。回退策略通常用于控制在重试尝试之间的等待时间,以防止连续的重试操作过于频繁。
RetryCallback(重试回调):RetryCallback 是一个接口,允许您定义需要重试的业务逻辑。您可以在 RetryCallback 中编写方法的执行逻辑,并在其中捕获可能导致失败的异常。
RecoveryCallback(补偿措施回调):所有重试都失败后,执行的托底补偿措施。
1、RetryTemplate核心逻辑
RetryListener:监听器提供了在重试时额外的回调。在其中我们可以加入自己的处理逻辑。
它定义了三个方法:
public interface RetryListener {
/**
* 在重试的第一次尝试之前调用。false将会否决重试流程,也不会执行业务代码。
例如,实现者可以设置需要由 {@link RetryOperations} 中的策略所需的状态。通过从此方法返回 false,可以否决整个重试过程,在这种情况下将抛出 {@link TerminatedRetryException}。
* @param回调返回的对象类型
* @param它声明可能会抛出的异常类型
* @param context 当前的 {@link RetryContext}。
* @param callback 当前的 {@link RetryCallback}。
* @return 如果应继续重试,则为 true。
*/
boolean open(RetryContext context, RetryCallbackcallback);
/**
* 在最后一次尝试之后调用。允许拦截器在控制返回给重试调用者之前清理它所持有的任何资源。
* @param context 当前的 {@link RetryContext}。
* @param callback 当前的 {@link RetryCallback}。
* @param throwable 回调抛出的最后一个异常。
* @param异常类型
* @param返回值
*/
void close(RetryContext context, RetryCallbackcallback, Throwable throwable);
/**
* 在每次不成功的重试尝试之后调用。
* @param context 当前的 {@link RetryContext}。
* @param callback 当前的 {@link RetryCallback}。
* @param throwable 回调抛出的最后一个异常。
* @param返回值
* @param要抛出的异常
*/
void onError(RetryContext context, RetryCallbackcallback, Throwable throwable);
}
retrytemplate核心方法:
/**
* 如果策略允许,执行一次回调操作,否则执行恢复回调。
* @param recoveryCallback 恢复回调(RecoveryCallback)
* @param retryCallback 重试回调(RetryCallback)
* @param state 重试状态(RetryState)
* @param <T> 返回值的类型
* @param <E> 要抛出的异常类型
* @throws ExhaustedRetryException 如果重试已经耗尽。
* @throws E 如果重试操作失败,则抛出异常
* @return T 重试后的值
*/
protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback,
RecoveryCallback<T> recoveryCallback, RetryState state) throws E, ExhaustedRetryException {
RetryPolicy retryPolicy = this.retryPolicy;
BackOffPolicy backOffPolicy = this.backOffPolicy;
// 无状态的:RetryPolicy初始化自己
// 有状态的:根据策略,每次新建/从缓存获取
RetryContext context = open(retryPolicy, state);
if (this.logger.isTraceEnabled()) {
this.logger.trace("RetryContext retrieved: " + context);
}
// 注册到threadlocal中,确保线程内全局可用
RetrySynchronizationManager.register(context);
Throwable lastException = null;
// 是否重试完
boolean exhausted = false;
try {
// 给客户端一个机会来增强上下文。调用注册的所有监听器,执行其open方法,
boolean running = doOpenInterceptors(retryCallback, context);
// 任意一个监听器open方法返回false,结果为false,抛出异常
if (!running) {
throw new TerminatedRetryException("Retry terminated abnormally by interceptor before first attempt");
}
// 获取或启动退避上下文
BackOffContext backOffContext = null;
Object resource = context.getAttribute("backOffContext");
if (resource instanceof BackOffContext) {
backOffContext = (BackOffContext) resource;
}
if (backOffContext == null) {
// 启用退避上下文
backOffContext = backOffPolicy.start(context);
if (backOffContext != null) {
context.setAttribute("backOffContext", backOffContext);
}
}
/*
* 是否可重试并且重试次数未耗尽。 业务逻辑可外部调用RetryContext.setExhaustedOnly进行干预
*/
while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
try {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Retry: count=" + context.getRetryCount());
}
// 重置上次异常,以便如果成功,关闭拦截器不会认为我们失败了...
lastException = null;
return retryCallback.doWithRetry(context);
}
catch (Throwable e) {
lastException = e;
try {
// 记录异常 RetryPolicy.registerThrowable,记录异常并增加重试次数
registerThrowable(retryPolicy, state, context, e);
}
catch (Exception ex) {
throw new TerminatedRetryException("Could not register throwable", ex);
}
finally {
// 异常增强器 RetryListener.onError
doOnErrorInterceptors(retryCallback, context, e);
}
if (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
try {
// 退避
backOffPolicy.backOff(backOffContext);
}
catch (BackOffInterruptedException ex) {
lastException = e;
// 被另一个线程阻止的退避 - 失败的重试
if (this.logger.isDebugEnabled()) {
this.logger.debug("Abort retry because interrupted: count=" + context.getRetryCount());
}
throw ex;
}
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Checking for rethrow: count=" + context.getRetryCount());
}
// 允许有状态的重试在此抛出异常,阻止重试
if (shouldRethrow(retryPolicy, context, state)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Rethrow in retry for policy: count=" + context.getRetryCount());
}
throw RetryTemplate.<E>wrapIfNecessary(e);
}
}
/*
* 但是如果有状态的重试走到这一步可能终止,比如断路器或回滚分类器。
*/
if (state != null && context.hasAttribute(GLOBAL_STATE)) {
break;
}
}
if (state == null && this.logger.isDebugEnabled()) {
this.logger.debug("Retry failed last attempt: count=" + context.getRetryCount());
}
exhausted = true;
// 重试完成且失败了,补偿措施 RecoveryCallback.recover
return handleRetryExhausted(recoveryCallback, context, state);
}
catch (Throwable e) {
throw RetryTemplate.<E>wrapIfNecessary(e);
}
finally {
// 资源回收
close(retryPolicy, context, state, lastException == null || exhausted);
doCloseInterceptors(retryCallback, context, lastException);
RetrySynchronizationManager.clear();
}
}
2、RetryPolicy重试策略
重试策略类
描述
MaxAttemptsRetryPolicy
设置最大的重试次数,超过之后执行recover
BinaryExceptionClassifierRetryPolicy
可以指定哪些异常需要重试,哪些异常不需要重试
SimpleRetryPolicy
支持次数和自定义异常
TimeoutRetryPolicy
在一段时间内(可配置)重试
ExceptionClassifierRetryPolicy
支持异常和重试策略的映射,不同异常可以使用不同策略
CompositeRetryPolicy
策略类的组合类,支持两种模式,乐观模式:只要一个重试策略满足即执行,悲观模式:所有策略模式满足再执行
CircuitBreakerRetryPolicy
熔断器模式
ExpressionRetryPolicy
符合表达式就重试
AlwaysRetryPolicy
一直重试
NeverRetryPolicy
从不重试
3、BackOffPolicy退避策略
策略类
描述
FixedBackOffPolicy
间隔固定时间重试。直接Thread.sleep固定时间
NoBackOffPolicy
无等待,立马重试
UniformRandomBackOffPolicy
在一个设置的时间区间内。随机等待后重试
ExponentialBackOffPolicy
在一个设置的时间区间内,等待时长为上一次时长的递增
ExponentialRandomBackOffPolicy
乘数随机的ExponentialBackOffPolicy
4、RetryState有状态的重试
/**
* 根据state获取RetryContext上下文
*
*/
protected RetryContext open(RetryPolicy retryPolicy, RetryState state) {
if (state == null) {
// 无状态的重试,每次生成新的RetryContext
return doOpenInternal(retryPolicy);
}
Object key = state.getKey();
if (state.isForceRefresh()) {
// 有状态的测试,如果要求强制刷新,也会重新生成。
return doOpenInternal(retryPolicy, state);
}
// 如果没有缓存命中,我们可以避免缓存重新填充的可能费用。
if (!this.retryContextCache.containsKey(key)) {
// 只有在发生失败时才使用缓存。
return doOpenInternal(retryPolicy, state);
}
RetryContext context = this.retryContextCache.get(key);
if (context == null) {
if (this.retryContextCache.containsKey(key)) {
throw new RetryException("Inconsistent state for failed item: no history found. "
+ "Consider whether equals() or hashCode() for the item might be inconsistent, "
+ "or if you need to supply a better ItemKeyGenerator");
}
// 缓存在containsKey()调用之间可能已经过期,所以我们必须接受这一点。
// 因为如果不是全局的state,会在重试执行完毕后,销毁相关资源包括RetryContext上下文。
// 所以如果没从retryContextCache获取到上下文,还是得重新创建一个
return doOpenInternal(retryPolicy, state);
}
// 为其他可能检查状态的人开始一个干净的状态
context.removeAttribute(RetryContext.CLOSED);
context.removeAttribute(RetryContext.EXHAUSTED);
context.removeAttribute(RetryContext.RECOVERED);
return context;
}
有状态的重试允许你在不同的重试中共享相同的上下文,可以让你依赖先前的结果。
5、CircuitBreakerRetryPolicy断路器实现
CircuitBreakerRetryPolicy核心方法
public boolean canRetry(RetryContext context) {
CircuitBreakerRetryContext circuit = (CircuitBreakerRetryContext) context;
if (circuit.isOpen()) {
// 断路器处于打开状态,增加断路次数
circuit.incrementShortCircuitCount();
return false;
}
else {
circuit.reset();
}
// 交于实际策略类判断是否能重试
return this.delegate.canRetry(circuit.context);
}
// 断路器的retry上下文信息
static class CircuitBreakerRetryContext extends RetryContextSupport {
// 持有委托的重试策略的上下文信息
private volatile RetryContext context;
private final RetryPolicy policy;
// 断路器上下文启动的时间
private volatile long start = System.currentTimeMillis();
// 断路器的重置超时时间,即在断路器打开后,经过了这段时间后会自动尝试重新关闭断路器。
private final long timeout;
// 触发断路器打开的时间窗口。如果委托的重试策略无法进行重试,并且从上下文启动以来的时间少于这个时间窗口,那么断路器将被打开。
private final long openWindow;
// 断路器打开状态下的熔断次数
private final AtomicInteger shortCircuitCount = new AtomicInteger();
public CircuitBreakerRetryContext(RetryContext parent, RetryPolicy policy, long timeout, long openWindow) {
super(parent);
this.policy = policy;
this.timeout = timeout;
this.openWindow = openWindow;
this.context = createDelegateContext(policy, parent);
// 设置为全局可重试状态,重试完毕后,也不会回收上下文信息,会一直在缓存中
setAttribute("state.global", true);
}
public void reset() {
shortCircuitCount.set(0);
setAttribute(CIRCUIT_SHORT_COUNT, shortCircuitCount.get());
}
public void incrementShortCircuitCount() {
shortCircuitCount.incrementAndGet();
setAttribute(CIRCUIT_SHORT_COUNT, shortCircuitCount.get());
}
private RetryContext createDelegateContext(RetryPolicy policy, RetryContext parent) {
RetryContext context = policy.open(parent);
reset();
return context;
}
public boolean isOpen() {
long time = System.currentTimeMillis() - this.start;
boolean retryable = this.policy.canRetry(this.context);
if (!retryable) {
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) {
if (!hasAttribute(CIRCUIT_OPEN) || (Boolean) getAttribute(CIRCUIT_OPEN) == false) {
// 不包含断路器打开标志,标记断路器打开,重置start时间。
// 时间间隔短,第一次熔断
logger.trace("Opening circuit");
setAttribute(CIRCUIT_OPEN, true);
this.start = System.currentTimeMillis();
}
return true;
}
}
else {
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;
}
@Override
public int getRetryCount() {
return this.context.getRetryCount();
}
@Override
public String toString() {
return this.context.toString();
}
}
6、@Retryable的实现原理
@EnableRetry中使用@Import(RetryConfiguration.class),引入RetryConfiguration配置类。
@Override
public void afterPropertiesSet() throws Exception {
this.retryContextCache = findBean(RetryContextCache.class);
this.methodArgumentsKeyGenerator = findBean(MethodArgumentsKeyGenerator.class);
this.newMethodArgumentsIdentifier = findBean(NewMethodArgumentsIdentifier.class);
this.retryListeners = findBeans(RetryListener.class);
this.sleeper = findBean(Sleeper.class);
Set<Class<? extends Annotation>> retryableAnnotationTypes = new LinkedHashSet<Class<? extends Annotation>>(1);
retryableAnnotationTypes.add(Retryable.class);
this.pointcut = buildPointcut(retryableAnnotationTypes);
this.advice = buildAdvice();
if (this.advice instanceof BeanFactoryAware) {
((BeanFactoryAware) this.advice).setBeanFactory(this.beanFactory);
}
}
RetryConfiguration类设置切入点为@Retryable标记的方法,切面为AnnotationAwareRetryOperationsInterceptor。
public Object invoke(MethodInvocation invocation) throws Throwable {
MethodInterceptor delegate = getDelegate(invocation.getThis(), invocation.getMethod());
if (delegate != null) {
return delegate.invoke(invocation);
}
else {
return invocation.proceed();
}
}
private MethodInterceptor getDelegate(Object target, Method method) {
ConcurrentMap<Method, MethodInterceptor> cachedMethods = this.delegates.get(target);
if (cachedMethods == null) {
cachedMethods = new ConcurrentHashMap<Method, MethodInterceptor>();
}
MethodInterceptor delegate = cachedMethods.get(method);
if (delegate == null) {
MethodInterceptor interceptor = NULL_INTERCEPTOR;
Retryable retryable = AnnotatedElementUtils.findMergedAnnotation(method, Retryable.class);
if (retryable == null) {
retryable = classLevelAnnotation(method, Retryable.class);
}
if (retryable == null) {
retryable = findAnnotationOnTarget(target, method, Retryable.class);
}
if (retryable != null) {
// 如果注解自定义interceptor,则使用该interceptor
if (StringUtils.hasText(retryable.interceptor())) {
interceptor = this.beanFactory.getBean(retryable.interceptor(), MethodInterceptor.class);
}
// 如果是有状态的,使用StatefulRetryOperationsInterceptor
else if (retryable.stateful()) {
interceptor = getStatefulInterceptor(target, method, retryable);
}
else {
// 默认使用无状态,RetryOperationsInterceptor
interceptor = getStatelessInterceptor(target, method, retryable);
}
}
cachedMethods.putIfAbsent(method, interceptor);
delegate = cachedMethods.get(method);
}
this.delegates.putIfAbsent(target, cachedMethods);
return delegate == NULL_INTERCEPTOR ? null : delegate;
}
执行业务方法时,被AnnotationAwareRetryOperationsInterceptor,根据注解配置,获取到对应的
MethodInterceptor。
如果注解自定义interceptor,则使用该interceptor
如果是有状态的,使用StatefulRetryOperationsInterceptor
默认使用无状态,RetryOperationsInterceptor
RetryOperationsInterceptor和StatefulRetryOperationsInterceptor持有委托对象retryOperations(是RetryTemplate),实际方法执行委托给RetryTemplate执行。
spring retry分为三大模块,主要是RetryPolicy、BackOffPolicy、RecoveryCallback,大家如果需要扩展也主要从这三块着手,RetryListener也在一定程度上让用户增强自己的业务逻辑