spring-retry使用以及源码

1、介绍

spring retry是从spring batch独立出来的一个能功能,主要实现了重试和熔断,对于重试是有场景限制的,不是什么场景都适合重试, 比如参数校验不合法、写操作等(要考虑写是否幂等)都不适合重试。 比如外部 RPC 调用,或者数据入库等操作,如果一次操作失败,可以进行多次重试,提高调用成功的可能性。

2、框架介绍

img

3、概念类

1、BackOff:

补偿值,一般指失败后多久进行重试的延迟值。

2、Sleeper:

暂停应用的工具,通常用来应用补偿值。

3、BackOffPolicy:

退避策略,决定失败后如何确定补偿值。是立即重试还是等待一段时间后重试,比如是网络错误,立即重试将导致立即失败,最好等待一小段时间后重试,还要防止很多服务同时重试导致DDos

在这里插入图片描述

BackOffPolicy 提供了如下策略实现:

1、NoBackOffPolicy

​ 无退避算法策略,即当重试时是立即重试;

protected void doBackOff() throws BackOffInterruptedException {
}

@Override
public String toString() {
   return "NoBackOffPolicy []";
}
2、FixedBackOffPolicy

​ 固定时间的退避策略,需设置参数sleeper和backOffPeriod,sleeper指定等待策略,默认是Thread.sleep,即线程休眠,backOffPeriod指定休眠时间,默认1秒;

/**
 * Default back off period - 1000ms.
 */
private static final long DEFAULT_BACK_OFF_PERIOD = 1000L;

/**
 * The back off period in milliseconds. Defaults to 1000ms.
 */
private volatile long backOffPeriod = DEFAULT_BACK_OFF_PERIOD;

private Sleeper sleeper = new ThreadWaitSleeper();

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

/**
 * Public setter for the {@link Sleeper} strategy.
 * @param sleeper the sleeper to set defaults to {@link ThreadWaitSleeper}.
 */
public void setSleeper(Sleeper sleeper) {
   this.sleeper = sleeper;
}

/**
 * Set the back off period in milliseconds. Cannot be < 1. Default value is 1000ms.
 * @param backOffPeriod the back off period
 */
public void setBackOffPeriod(long backOffPeriod) {
   this.backOffPeriod = (backOffPeriod > 0 ? backOffPeriod : 1);
}

/**
 * The backoff period in milliseconds.
 * @return the backoff period
 */
public long getBackOffPeriod() {
   return backOffPeriod;
}

/**
 * Pause for the {@link #setBackOffPeriod(long)}.
 * @throws BackOffInterruptedException if interrupted during sleep.
 */
protected void doBackOff() throws BackOffInterruptedException {
   try {
      sleeper.sleep(backOffPeriod);
   }
   catch (InterruptedException e) {
      throw new BackOffInterruptedException("Thread interrupted while sleeping", e);
   }
}
3、UniformRandomBackOffPolicy

​ 随机时间退避策略,需设置sleeper、minBackOffPeriod和maxBackOffPeriod,该策略在[minBackOffPeriod,maxBackOffPeriod之间取一个随机休眠时间,minBackOffPeriod默认500毫秒,maxBackOffPeriod默认1500毫秒;

private static final long DEFAULT_BACK_OFF_MIN_PERIOD = 500L;

/**
* Default max back off period - 1500ms.
*/
private static final long DEFAULT_BACK_OFF_MAX_PERIOD = 1500L;

private volatile long minBackOffPeriod = DEFAULT_BACK_OFF_MIN_PERIOD;

private volatile long maxBackOffPeriod = DEFAULT_BACK_OFF_MAX_PERIOD;

private Random random = new Random(System.currentTimeMillis());

private Sleeper sleeper = new ThreadWaitSleeper();

public UniformRandomBackOffPolicy withSleeper(Sleeper sleeper) {
    UniformRandomBackOffPolicy res = new UniformRandomBackOffPolicy();
    res.setMinBackOffPeriod(minBackOffPeriod);
    res.setSleeper(sleeper);
    return res;
}
    
protected void doBackOff() throws BackOffInterruptedException {
   try {
      long delta = maxBackOffPeriod==minBackOffPeriod ? 0 : random.nextInt((int) (maxBackOffPeriod - minBackOffPeriod));
      sleeper.sleep(minBackOffPeriod + delta );
   }
   catch (InterruptedException e) {
      throw new BackOffInterruptedException("Thread interrupted while sleeping", e);
   }
}
4、ExponentialBackOffPolicy

​ 指数退避策略,需设置参数sleeper、initialInterval、maxInterval和multiplier,initialInterval指定初始休眠时间,默认100毫秒,maxInterval指定最大休眠时间,默认30秒,multiplier指定乘数,即下一次休眠时间为当前休眠时间*multiplier;

protected final Log logger = LogFactory.getLog(this.getClass());
/**
 * The default 'initialInterval' value - 100 millisecs. Coupled with the default
 * 'multiplier' value this gives a useful initial spread of pauses for 1-5 retries.
 */
public static final long DEFAULT_INITIAL_INTERVAL = 100L;

/**
 * The default maximum backoff time (30 seconds).
 */
public static final long DEFAULT_MAX_INTERVAL = 30000L;

/**
 * The default 'multiplier' value - value 2 (100% increase per backoff).
 */
public static final double DEFAULT_MULTIPLIER = 2;

/**
 * The initial sleep interval.
 */
private volatile long initialInterval = DEFAULT_INITIAL_INTERVAL;

/**
 * The maximum value of the backoff period in milliseconds.
 */
private volatile long maxInterval = DEFAULT_MAX_INTERVAL;

/**
 * The value to increment the exp seed with for each retry attempt.
 */
private volatile double multiplier = DEFAULT_MULTIPLIER;

private Sleeper sleeper = new ThreadWaitSleeper();

/**
 * Public setter for the {@link Sleeper} strategy.
 * @param sleeper the sleeper to set defaults to {@link ThreadWaitSleeper}.
 */
public void setSleeper(Sleeper sleeper) {
   this.sleeper = sleeper;
}

public ExponentialBackOffPolicy withSleeper(Sleeper sleeper) {
   ExponentialBackOffPolicy res = newInstance();
   cloneValues(res);
   res.setSleeper(sleeper);
   return res;
}

public void backOff(BackOffContext backOffContext) throws BackOffInterruptedException {
	ExponentialBackOffContext context = (ExponentialBackOffContext) backOffContext;
try {
	long sleepTime = context.getSleepAndIncrement();
    
    if (logger.isDebugEnabled()) {
        logger.debug("Sleeping for " + sleepTime);
    }
	sleeper.sleep(sleepTime);
}catch (InterruptedException e) {
		throw new BackOffInterruptedException("Thread interrupted while sleeping", e);
	}
}
	
	
public synchronized long getSleepAndIncrement() {
    long sleep = this.interval;
    if (sleep > maxInterval) {
   		 sleep = maxInterval;
    }else {
    	this.interval = getNextInterval();
    }
   		 return sleep;
    }

protected long getNextInterval() {
	return (long) (this.interval * this.multiplier);
}

	
5、ExponentialRandomBackOffPolicy

​ 随机指数退避策略,引入随机乘数,之前说过固定乘数可能会引起很多服务同时重试导致DDos,使用随机休眠时间来避免这种情况。

public class ExponentialRandomBackOffPolicy extends ExponentialBackOffPolicy {
   /**
    * Returns a new instance of {@link org.springframework.retry.backoff.BackOffContext},
    * seeded with this policies settings.
    */
   public BackOffContext start(RetryContext context) {
      return new ExponentialRandomBackOffContext(getInitialInterval(), getMultiplier(),
            getMaxInterval());
   }

   protected ExponentialBackOffPolicy newInstance() {
      return new ExponentialRandomBackOffPolicy();
   }

   static class ExponentialRandomBackOffContext
         extends ExponentialBackOffPolicy.ExponentialBackOffContext {
      private final Random r = new Random();

      public ExponentialRandomBackOffContext(long expSeed, double multiplier,
            long maxInterval) {
         super(expSeed, multiplier, maxInterval);
      }

      @Override
      public synchronized long getSleepAndIncrement() {
         long next = super.getSleepAndIncrement();
         next = (long) (next * (1 + r.nextFloat() * (getMultiplier() - 1)));
         return next;
      }

   }
}

4、RetryContext:

​ 重试上下文,代表了能被重试动作使用的资源。

​ 重试上下文接口为RetryContext,其中继承了AttributeAccessor接口,此接口提供了对扩展属性的增删查改操作,用于对不同重试情况下特性的支持,RetryContext实现由AttributeAccessorSupport类实现(map集合实现)。RetryContextSupport类提供了一些重试基础数据的记录,包括重试次数,重试上文,最新异常,是否结束等。默认实现为SimpleRetryContext。

SimpleRetryContext:

​ 默认实现;

NeverRetryContext:

​ 新增是否结束字段;TimeoutRetryContext:

​ 扩展开始时间和过期时间字段,判断是否过期

5、RetryPolicy:

​ 重试策略,决定失败能否重试。
在这里插入图片描述

RetryPolicy提供了如下策略实现:

1、NeverRetryPolicy:

​ 只允许调用RetryCallback一次,不允许重试;

public class NeverRetryPolicy implements RetryPolicy {

   /**
    * Returns false after the first exception. So there is always one try, and
    * then the retry is prevented.
    * 
    * @see org.springframework.retry.RetryPolicy#canRetry(org.springframework.retry.RetryContext)
    */
   public boolean canRetry(RetryContext context) {
      return !((NeverRetryContext) context).isFinished();
   }
   
   	private static class NeverRetryContext extends RetryContextSupport {
		private boolean finished = false;

		public NeverRetryContext(RetryContext parent) {
			super(parent);
		}

		public boolean isFinished() {
			return finished;
		}

		public void setFinished() {
			this.finished = true;
		}
	}
2、AlwaysRetryPolicy:

​ 允许无限重试,直到成功,此方式逻辑不当会导致死循环;

public class AlwaysRetryPolicy extends NeverRetryPolicy {

   /**
    * Always returns true.
    * 
    * @see org.springframework.retry.RetryPolicy#canRetry(org.springframework.retry.RetryContext)
    */
   public boolean canRetry(RetryContext context) {
      return true;
   }

}
3、SimpleRetryPolicy:

​ 固定次数重试策略,默认重试最大次数为3次,RetryTemplate默认使用的策略;

/**
 * The default limit to the number of attempts for a new policy.
 */
public final static int DEFAULT_MAX_ATTEMPTS = 3;

private volatile int maxAttempts;

private BinaryExceptionClassifier retryableClassifier = new BinaryExceptionClassifier(false);

/**
 * Create a {@link SimpleRetryPolicy} with the default number of retry
 * attempts, retrying all exceptions.
 */
public SimpleRetryPolicy() {
   this(DEFAULT_MAX_ATTEMPTS, Collections
         .<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true));
}

@Override
public boolean canRetry(RetryContext context) {
	Throwable t = context.getLastThrowable();
	return (t == null || retryForException(t)) && context.getRetryCount() < maxAttempts;
}
4、TimeoutRetryPolicy:

​ 超时时间重试策略,默认超时时间为1秒,在指定的超时时间内允许重试;

/**
 * Default value for timeout (milliseconds).
 */
public static final long DEFAULT_TIMEOUT = 1000;

private long timeout = DEFAULT_TIMEOUT;

public boolean canRetry(RetryContext context) {
	return ((TimeoutRetryContext) context).isAlive();
}

	
private static class TimeoutRetryContext extends RetryContextSupport {
	private long timeout;

	private long start;

	public TimeoutRetryContext(RetryContext parent, long timeout) {
		super(parent);
		this.start = System.currentTimeMillis();
		this.timeout = timeout;
	}

	public boolean isAlive() {
		return (System.currentTimeMillis() - start) <= timeout;
	}
}
5、CircuitBreakerRetryPolicy:

​ 有熔断功能的重试策略,需设置3个参数openTimeout、resetTimeout和delegate,具体擦看熔断源码

6、CompositeRetryPolicy:

​ 组合重试策略,有两种组合方式,乐观组合重试策略是指只要有一个策略允许重试即可以,悲观组合重试策略是指只要有一个策略不允许重试即可以,但不管哪种组合方式,组合中的每一个策略都会执行。

public boolean canRetry(RetryContext context) {
   RetryContext[] contexts = ((CompositeRetryContext) context).contexts;
   RetryPolicy[] policies = ((CompositeRetryContext) context).policies;

   boolean retryable = true;

   if(this.optimistic) {
      retryable = false;
      for (int i = 0; i < contexts.length; i++) {
         if (policies[i].canRetry(contexts[i])) {
            retryable = true;
         }
      }
   }
   else {
      for (int i = 0; i < contexts.length; i++) {
         if (!policies[i].canRetry(contexts[i])) {
            retryable = false;
         }
      }
   }

   return retryable;
}
7、ExceptionClassifierRetryPolicy:

​ 设置不同异常的重试策略,类似组合重试策略,区别在于这里只区分不同异常的重试

6、RecoveryCallback:

​ 定义一个动作recover,在重试耗尽后的动作。

当重试耗尽时,RetryOperations可以将控制权传递给另一个回调RecoveryCallback,要使用此功能,客户端只需将回调函数一起传递给相同的方法,

final RecoveryCallback<Object> recoveryCallback = new RecoveryCallback<Object>() {
    public Object recover(RetryContext context) throws Exception {
        System.out.println("do recory operation");
        System.out.println(context.getAttribute("key1"));
        return null;
    }
};

final Object execute = retryTemplate.execute(retryCallback, recoveryCallback);

7、RetryCallback:

​ 具体的重试动作。

8、RetryOperations:

​ 类主要实现了对重试的接口,RetryTemplate为其实现。主要包括execute方法,RetryCallback为重试需要执行的操作,RecoverCallback为重试结束后如何返回,比如提供默认值等等,即重试失败后需要进行的操作。RetryState retryState 重试状态,通常包含一个重试的键值,主要分为有状态和无状态重试,RetryTemplate默认的重试策略为SimpleRetryPlicy

<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback) throws E;

<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback) throws E;

<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RetryState retryState) throws E, ExhaustedRetryException;

<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback, RetryState retryState)throws E;

9、RetryTemplate

在这里插入图片描述

​ RetryOperations的具体实现,组合了RetryListener[],BackOffPolicy,RetryPolicy。具体执行doExecute

10、RetryState:

​ 有无重试状态,通常包含一个重试的键值。

1、无重试状态

​ 在最简单的情况下,重试只是一个while循环,RetryTemplate可以一直尝试,直到成功或失败。RetryContext包含一些状态来决定是重试还是中止,但是这个状态位于堆栈上,不需要将它存储在全局的任何位置,因此我们将此称为无状态重试。无状态重试和有状态重试之间的区别包含在RetryPolicy的实现中(RetryTemplate可以同时处理这两种情况),在无状态重试中,回调总是在重试失败时在同一个线程中执行。

2、有重试状态

如果失败导致事务性资源无效,则需要特别考虑,这并不适用于简单的远程调用,因为(通常)没有事务资源,但有时确实适用于数据库更新,尤其是在使用有事务的时候,在这种情况下,只有立即重新抛出调用失败的异常才有意义,以便事务可以回滚并启动一个新的有效的事务。

在这些情况下,无状态重试是不够的,因为重新抛出和回滚必然会离开RetryOperations.execute()方法,并可能丢失堆栈上的上下文。为了避免丢失它,我们必须引入一种存储策略,将它从堆栈中取出并(至少)放入堆存储中,为此,Spring Retry提供了一种存储策略RetryContextCache,可以将其注入RetryTemplate,RetryContextCache的默认实现在内存中,使用一个简单的Map,它有一个严格执行的最大容量,以避免内存泄漏,但它没有任何高级缓存功能,如生存时间。如果需要,应该考虑注入具有这些特性的

3、有状态重试情况

​ 主要分为事务回滚和熔断

比如:

​ 1、如数据库操作异常DataAccessException,则不能执行重试,而如果抛出其他异常可以重试。

​ 2、 熔断的意思不在当前循环中处理重试,而是全局重试模式(不是线程上下文)。熔断会跳出循环,那么必然会丢失线程上下文的堆栈信息。那么肯定需要一种“全局模式”保存这种信息,目前的实现放在一个cache(map实现的)中,下次从缓存中获取就能继续重试了。

3.1 事务代码:

在这里插入图片描述

RetryTemplate retryTemplate = new RetryTemplate();
//重试策略
 SimpleRetryPolicy policy = new SimpleRetryPolicy(3, Collections.singletonMap(DataAccessException.class, true));
//退避策略
 FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(100);

retryTemplate.setRetryPolicy(policy);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);


//有状态 事务
BinaryExceptionClassifier classifier = new BinaryExceptionClassifier( Collections.singleton(DataAccessException.class));
RetryState state =new DefaultRetryState("mykey",false, classifier);


final RetryCallback<Object, Exception> retryCallback = new RetryCallback<Object, Exception>() {
    public Object doWithRetry(RetryContext context) throws DataAccessException {
        System.out.println("do some thing");
        //设置context一些属性,给RecoveryCallback传递一些属性
        context.setAttribute("key1", "value1");
        System.out.println(context.getRetryCount());
        throw new CannotAcquireLockException("exception");
    }
};

// 如果RetryCallback执行出现指定异常, 并且超过最大重试次数依旧出现指定异常的话,就执行RecoveryCallback动作
final RecoveryCallback<Object> recoveryCallback = new RecoveryCallback<Object>() {
    public Object recover(RetryContext context) throws DataAccessException {
        System.out.println("do recory operation");
        System.out.println(context.getAttribute("key1"));
        return null;
    }
};

try {
    final Object execute = retryTemplate.execute(retryCallback, recoveryCallback,state);
} catch (Exception e) {
    e.printStackTrace();
}
3.2 熔断器代码:

public static void main(String[] args) {
     RetryTemplate retryTemplate = new RetryTemplate();
    //重试策略  delegate是真正判断是否重试的策略,当重试失败时,则执行熔断策略
     CircuitBreakerRetryPolicy retryPolicy  = new CircuitBreakerRetryPolicy(new SimpleRetryPolicy(3));
     //openWindow,配置熔断器电路打开的超时时间,当超过openTimeout之后熔断器电路变成半打开状态(主要有一次重试成功,则闭合电路)
    retryPolicy.setOpenTimeout(5000);
    //timeout,配置重置熔断器重新闭合的超时时间。
    retryPolicy.setResetTimeout(20000);
    retryTemplate.setRetryPolicy(retryPolicy);


    for (int i = 0; i <10 ; i++) {
            try {
                Object key = "circuit";
                boolean isForceRefresh = false;
                RetryState state = new DefaultRetryState(key, isForceRefresh);


                //退避策略
                FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
                fixedBackOffPolicy.setBackOffPeriod(100);
                retryTemplate.setBackOffPolicy(fixedBackOffPolicy);


                 RetryCallback<Object, RuntimeException> retryCallback = new RetryCallback<Object, RuntimeException>() {
                    public Object doWithRetry(RetryContext context) throws RuntimeException {
                        System.out.println("do some thing");
                        //设置context一些属性,给RecoveryCallback传递一些属性

                        context.setAttribute("key1", context.getRetryCount());
                        System.out.println(context.getRetryCount());
                        System.out.println(context.getAttribute("key1").toString()+"--");

                        if(context.getAttribute("key1").toString().equals("2")){
                            System.out.println(context.getAttribute("key1").toString()+"-----------------------");
                            return null;
                        }else{
                            throw new RuntimeException("exception");
                        }
                    }
                };

                // 如果RetryCallback执行出现指定异常, 并且超过最大重试次数依旧出现指定异常的话,就执行RecoveryCallback动作
                 RecoveryCallback<Object> recoveryCallback = new RecoveryCallback<Object>() {
                    public Object recover(RetryContext context) throws RuntimeException {
                        System.out.println("do recory operation");
                        System.out.println(context.getAttribute("key1"));
                        System.out.println(context.getAttribute("key1---------------------------------"));
                        return null;
                    }
                };


                 Object execute = retryTemplate.execute(retryCallback, recoveryCallback,state);
                System.out.println(execute);
            }catch (Exception e){

            }
    }

在这里插入图片描述

后面的循环一直是处于失败的,不会重新进行执行,断路器被打开

这里由于设置了isForceRefresh = false,则key = "circuit"的值(也就是RetryContext)会从缓存中获取,所以当重试失败且满足this.time < this.openWindow发生熔断的时候,后面仍然可以继续已全局模式实现重试(拿到的RetryContext是同一个)。

11、RetryStatistics和RetryListener

​ 用来监控Retry的执行情况,并生成统计信息。

典型的“监听者”,在重试的不同阶段通知“监听者”(例如doSth,wait等阶段时通知)

4、注解基本使用

1、pom新增

<!--retry-->
<dependency>
  <groupId>org.springframework.retry</groupId>
  <artifactId>spring-retry</artifactId>
  <version>1.2.4.RELEASE</version>
</dependency>

<!--AOP-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
</dependency>

2、在启动类上添加打开注解

@EnableRetry
/**
 * value:指定失敗重試的Exception類型
 * include:和value效果一樣,當exclude為空時,所有Exceoption都會重試,預設空
 * exclude:指定Exceoption不重試,當include為空時,所有Exceoption都會重試,預設空
 * maxAttempts:指定重試的次數,預設3
 * backoff:重試補償機制,默认使用FixedBackOffPolicy(指定等待时间),重试等待1000ms
 *         @Backoff
 *              delay:指定延遲後重試
 *              multiplier:指定延遲的倍数,例如:delay=3000l, multiplier=2,第一次重試為3秒,第二次為6秒,第三次為12秒
 **/
@Retryable(value = DemoException.class, maxAttempts = 4, backoff = @Backoff(delay = 3000l,multiplier = 2))
public void testSpringRetry() throws DemoException {
    System.out.println("输入------------------------------------------------------------------");
    throw new DemoException();
}
// 用于@Retryable重试失败后处理方法,此注解注释的方法参数一定要是@Retryable抛出的异常,否则无法识别,可以在该方法中进行日志处理
	@Recover
    public String recover(DemoException demoException, Integer id){
      // 全部重试失败处理
    }
@Retryable注解参数
参数含义
interceptor重试拦截器bean名称,用于可重试方法
value可重试的异常类型。含义同include。默认为空(如果excludes也为空,则重试所有异常)
include可重试的异常类型。默认为空(如果excludes也为空,则重试所有异常)
exclude无需重试的异常类型。默认为空(如果includes也为空,则重试所有异常)
label统计报告的唯一标签。如果未提供,
则调用者可以选择忽略它或提供一个默认值。
我的理解就是这个重试方法的唯一名称
stateful若为true,标志重试是有状态的:即重新抛出异常,
但是重试策略与相同的策略应用于具有相同参数的后续调用。
若为false,则不会重新引发可重试的异常
maxAttempts最大重试次数(包括第一次失败),默认为3次
maxAttemptsExpression计算最大尝试次数(包括第一次失败)的表达式,默认为3 次
backoff重试等待策略,下面会在@Backoff中介绍
exceptionExpression指定在SimpleRetryPolicy.canRetry()返回true之后要求值的表达式-可用于有条件地禁止重试。
@Backoff注解
参数
value
delay重试之间的等待时间(以毫秒为单位)
maxDelay重试之间的最大等待时间(以毫秒为单位)
multiplier指定延迟的倍数
delayExpression重试之间的等待时间表达式
maxDelayExpression重试之间的最大等待时间表达式
multiplierExpression指定延迟的倍数表达式
random随机指定延迟时间

5、基本使用

public class retryTest {

    public static void main(String[] args) {
        final RetryTemplate retryTemplate = new RetryTemplate();
        final SimpleRetryPolicy policy = new SimpleRetryPolicy(3, Collections.<Class<? extends Throwable>, Boolean>
                singletonMap(Exception.class, true));
        FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
        fixedBackOffPolicy.setBackOffPeriod(100);
        retryTemplate.setRetryPolicy(policy);
        retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
        final RetryCallback<Object, Exception> retryCallback = new RetryCallback<Object, Exception>() {
            public Object doWithRetry(RetryContext context) throws Exception {
                System.out.println("do some thing");
                //设置context一些属性,给RecoveryCallback传递一些属性
                context.setAttribute("key1", "value1");
                System.out.println(context.getRetryCount());
                throw new Exception("exception");
                //                return null;
            }
        };

        // 如果RetryCallback执行出现指定异常, 并且超过最大重试次数依旧出现指定异常的话,就执行RecoveryCallback动作
        final RecoveryCallback<Object> recoveryCallback = new RecoveryCallback<Object>() {
            public Object recover(RetryContext context) throws Exception {
                System.out.println("do recory operation");
                System.out.println(context.getAttribute("key1"));
                return null;
            }
        };

        // 设置listener
        retryTemplate.setListeners(new RetryListener[]{new RetryListener() {
            @Override
            public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
                System.out.println("RetryListener-open");
                return true;
            }

            @Override
            public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback,
                                                       Throwable throwable) {
                System.out.println("RetryListener-close");
            }

            @Override
            public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback,
                                                         Throwable throwable) {
                System.out.println("RetryListener-onError");
            }
        }});

        try {
            final Object execute = retryTemplate.execute(retryCallback, recoveryCallback);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

6、源码分包

1、classify

我们发现这个包里面还有一个和retry同级的classify包,显然它应该是retry需要用到,但是又不是包含的retry模型里面的东西

classify包作为retry的辅助类,主要应用于RetryPolicy的canRetry()方法中,通过比较捕获的异常与定义的异常直接关系,决定是否符合重试条件

在这里插入图片描述

1、Classifier<C, T> 接口:

​ 是整个包的核心接口,定义了 T classify(C classifiable);把一个C类型对象,转换为T类型对象,其中T类型通常是枚举类型或者布尔类型这种可以直接比较结果的。

2、ClassifierSupport<C, T> 类:

​ 一个基础实现,引入默认值机制,无论要传入任何C类型对象,都返回默认的T类型对象。

3、ClassifierAdapter<C, T> 类:

​ 定义了两种方式的转换,一种直接Classifier,在需要转换时候调用,一种传入通过识别一个目标类中@Classifier注解的方法,把它作为转换的实现类,在需要转换时候调用。

4、SubclassClassifier<T, C>类:

​ 首先要注意这里T和C对调写了,实现了能识别一个类和其子类都能被识别到,转换为目标类型对象的机制。这对于retry需要的异常类的转换十分重要,通常我们只需要定义某一类的异常重试策略,那么其子类异常也会同样应用到该策略,比如我们定义了数据库错误SQLException需要重试,实际运行可能抛出的是SQLTimeoutException,或者BatchUpdateException,它们就都会被捕获重试。

5、BinaryExceptionClassifier类:

​ 明确化了SubclassClassifier<T, C>的类型,其classify()方法把Throwable转换为Boolean。代码如下:

@Override
public Boolean classify(Throwable classifiable) {
   Boolean classified = super.classify(classifiable);
   if (!this.traverseCauses) {
      return classified;
   }

   /*
    * If the result is the default, we need to find out if it was by default
    * or so configured; if default, try the cause(es).
    */
   if (classified.equals(this.getDefault())) {
      Throwable cause = classifiable;
      do {
         if (this.getClassified().containsKey(cause.getClass())) {
            return classified; // non-default classification
         }
         cause = cause.getCause();
         classified = super.classify(cause);
      }
      while (cause != null && classified.equals(this.getDefault()));
   }

   return classified;
}

​ 如果traverseCauses为false,就简单调用父类进行转换即可,如果为真,就必须一直找Throwable的Cause链条,直到找到匹配的转换。

6、PatternMatcher和PatternMatchingClassifier :

能够把符合样式的字符串转换为T对象的转换器。

其核心方法为imatch(),对?和*进行了处理判断,判断输入的str是否符合某种样式pattern。

7、BackToBackPatternClassifier<C, T> 类:

背对背映射组合,先吧C对象转换为string,然后再把string转换为T对象。

2、retry顶级接口

1、RetryContext接口:

​ 从图上可以看到,重试上下文处于核心位置,作为核心数据接口,储存了重试所需要的各类信息。

​ RetryContext getParent();

​ 从获取父上下文方法可知,它是一个链式结构。

2、RetryPolicy接口:
 //判断一个上下文能否重试
boolean canRetry(RetryContext context);

//开启一个重试上下文环境
RetryContext open(RetryContext parent);

 //关闭一个重试上下文环境
void close(RetryContext context);

//出现异常时候,把异常登记到上下文中
void registerThrowable(RetryContext context, Throwable throwable);

从接口参数可以看出,策略都是根据上下文情况进行判断分析的。

3、RetryOperations接口:

​ 各种花式execute(),根据可配置的重试行为,进行方法的执行,其具体的实现就是核心类RetryTemplate

4、RetryListener接口:

​ 作为重试动作的监听器,给spring-retry加点料,用在统计机制上。监听3类动作:open()在开启操作之前,close()在关闭操作之后,onError()在出现错误时。

5、RetryStatistics接口:

​ 记录重试统计信息的接口。登记完成数、开始数、错误数、中止数、恢复数。

6、RetryException及ExhaustedRetryException,TerminatedRetryException异常

​ 定义了retry项目内部可能抛出的异常,RetryException是基类。

3、backoff包

在这里插入图片描述

1、BackOffPolicy接口:

​ 该包的核心接口,包含两个方法,一是生成一个当前补偿上下文环境,二是进行补偿动作

根据重试上下文生成一个补偿上下文
BackOffContext start(RetryContext context);

//根据补偿上下文执行延迟操作,可能抛出中断异常
void backOff(BackOffContext backOffContext) throws BackOffInterruptedException;
2、Sleeper接口与ThreadWaitSleeper类

​ 真正的补偿动作具体执行器, ThreadWaitSleeper就是调用了Thread.sleep()方法进行延迟

3、StatelessBackOffPolicy抽象类

​ 其start方法返回null,也就是没有重试上下文,执行backOff时候调用的是无参数的doBackOff()。换句话说,代表具体补偿动作是固定的,并不依赖上下文环境。

4、NoBackOffPolicy类

​ 最简单的默认策略,具体延迟为空操作,也就是不补偿,不延迟。

5、SleepingBackOffPolicy接口

​ 有一个withSleeper()方法,传入一个Sleeper。

6、UniformRandomBackOffPolicy类

​ 标准的随机延迟策略,给定最小值,最大值(默认为500L,1500L),会在这个区间里面随机进行补偿延迟。

7、 FixedBackOffPolicy类

​ 标准的固定延迟策略,每次延迟固定时间(默认1000L)

​ ExponentialBackOffPolicy类和ExponentialRandomBackOffPolicy:

​ 这两个类都是SleepingBackOffPolicy的实现,内部用ThreadWaitSleeper延迟。实现的是延迟指数倍增的效果,区别是ExponentialBackOffPolicy是固定倍增,ExponentialRandomBackOffPolicy加入了随机性。

4、context包

该包只有一个类RetryContextSupport,重试上下文的具体实现。

  1. 扩展AttributeAccessorSupport:内部有个linkedHashMap存储标准属性外的其他属性
  2. 有parent属性,在构造时候传入其父上下文,这样就维护了一个链表信息,方便后续查找。
  3. count和lastException是记录型属性,登记这个上下文的状态。

5、stats包

在这里插入图片描述

重试的统计信息

1、MutableRetryStatistics接口:

​ 增加了incrementc××()方法,增加统计值

2、DefaultRetryStatistics类:

​ 统计的默认实现,定义了一堆的AtomicInterger存储统计值,同时扩展自AttributeAccessorSupport,还能存放其他信息。

3、ExponentialAverageRetryStatistics类:

​ 增加了指数平均指标的统计值,

4、RetryStatisticsFactory接口和DefaultRetryStatisticsFactory实现:

​ 就一个create()方法,构造MutableRetryStatistics。默认是生产ExponentialAverageRetryStatistics统计类。

5、StatisticsRepository接口:

​ 统计仓库,可以存放多个统计信息的接口

6、DefaultStatisticsRepository类:

​ 统计仓库的默认实现,内部有一个DefaultRetryStatisticsFactory,如果找不到对应名字的统计,就由工厂生产一个。

6、listener包

在这里插入图片描述

就一个类RetryListenerSupport,其具体实现子类StatisticsListener位于stats包中。主要监听close()和onError()事件,并调用repository进行记录统计信息。

7、support包

1、DefaultRetryState类

​ 代表了一个新的重试尝试的状态。包含3个属性:

 //该状态的键值
final private Object key;
 //是否需要强制刷新
final private boolean forceRefresh;
 //回滚的转换器,当转换为true值是,这个异常将会引起回滚操作
final private Classifier<? super Throwable, Boolean> rollbackClassifier;

有个rollbackFor()用来判断某个异常是否需要引起回滚,如果没有rollbackClassifier,默认返回true,否则按照rollbackClassifier转换值进行判断。

2、RetrySimulation类

​ 代表一个发生器,有个SleepSequence内部类,代表一组sleep值,有最长的,有总和的。而发生器根据序列维护内部的sleepHistogram直方图,在获得百分比是能返回对应值。

3、RetrySimulator类

​ 用来执行补偿+重试的操作的工具类。在构造函数中传入SleepingBackOffPolicy和RetryPolicy作为内部属性。在executeSingleSimulation()方法中,设置好补偿机制和重置策略,然后直接通过template执行失败的FailingRetryException,模拟失败的动作,进行补偿和重试的组合操作。

4、RetrySynchronizationManager类

​ 在ThreadLocal中存放RetryContext,用来保证一个线程只能维护一个重试上下文,进行一个重试操作。毕竟Sleep是用Tread.sleep,如果多个重试,这个补偿机制就无法生效了。

5、RetryTemplate类

​ 这个是这个包最重要的一个类了,之前看到重试策略,回退策略,缓存、监听器都是应用在这里。

​ a.它实现了RetryOperations接口。

​ b.很有风格的是,execute是给外部调用的,真正内部起作用的是doExecute()

8、interceptor包

​ 主要是为了利用AOP机制,把任意声明为@Retryable的方法都变成可以可重试的

​ interceptor包和annotation紧密相关

1、MethodArgumentsKeyGenerator接口

​ Object getKey(Object[] item);

​ 传入item通常为方法的参数,返回的Object为这些参数的唯一标识

2、FixedKeyGenerator类

​ 其简单实现,无论何种参数数组传入,都返回给定的Label值。

3、MethodInvocationRecoverer接口

​ 定义回复接口方法,具体声明如下:

​ T recover(Object[] args, Throwable cause);

4、NewMethodArgumentsIdentifier接口

​ 区别判断一组参数,之前是否执行过

5、RetryOperationsInterceptor类

由于Retry是利用AOP机制实现的,因而需要定义MethodInterceptor把我们声明的方法绑定到RetryTemplate的调用中去。

public Object invoke(final MethodInvocation invocation) throws Throwable { //先取到该方法调用的名字、

   String name;
   if (StringUtils.hasText(label)) {
      name = label;
   } else {
      name = invocation.getMethod().toGenericString();
   }
   final String label = name; //构造回调函数

   RetryCallback<Object, Throwable> retryCallback = new RetryCallback<Object, Throwable>() {

      public Object doWithRetry(RetryContext context) throws Exception { // 在上下文中登记Label 
         
         context.setAttribute(RetryContext.NAME, label);

         /*
          * If we don't copy the invocation carefully it won't keep a reference to
          * the other interceptors in the chain. We don't have a choice here but to
          * specialise to ReflectiveMethodInvocation (but how often would another
          * implementation come along?).
          */
         if (invocation instanceof ProxyMethodInvocation) { // 多一重保险,判断是代理方法调用  
            try {
               return ((ProxyMethodInvocation) invocation).invocableClone().proceed(); //实际利用动态方法调用,执行方法本身
            }
            catch (Exception e) {  // 捕捉后重新抛出 
               throw e;
            }
            catch (Error e) { // 捕捉后重新抛出      
               throw e;
            }
            catch (Throwable e) { // 其他错误,就是非法错误了。
               throw new IllegalStateException(e);
            }
         }
         else {
            throw new IllegalStateException(
                  "MethodInvocation of the wrong type detected - this should not happen with Spring AOP, " +
                        "so please raise an issue if you see this exception");
         }
      }

   };
 	// 判断有无恢复方法,如果有,就构造一个恢复回调
   if (recoverer != null) {
      ItemRecovererCallback recoveryCallback = new ItemRecovererCallback(
            invocation.getArguments(), recoverer);
             // 实际还是传入RetryTemplate执行方法调用
      return this.retryOperations.execute(retryCallback, recoveryCallback);
   }
	
	//执行retryCallback具体操作
   return this.retryOperations.execute(retryCallback);

}
6、StatefulRetryOperationsInterceptor类

​ RetryOperationsInterceptor类是公用一个RetryTemplate的。而又状态的RetryOperationsInterceptor就必须每个实例都有自己的RetryTemplate,再配合RetryState决定是否需要抛出RollbackException了。其核心invoke方法如下:

7、RetryInterceptorBuilder类

​ 流式构造的工厂RetryInterceptor类。具体使用的例子如下:

StatefulRetryOperationsInterceptor interceptor = RetryInterceptorBuilder.stateful() //构造有状态的Interceptor       
        .maxAttempts(5)
        .backOffOptions(1, 2, 10) // initialInterval, multiplier,      
        .build();

​ 这个工厂能生产3种不同的Interceptor,StatefulRetryInterceptor(有状态的),StatelessRetryInterceptor(无状态),CircuitBreakerInterceptor(有状态加熔断)。

8、RecoverAnnotationRecoveryHandler类

​ MethodInvocationRecoverer的实现,根据一个方法,查找对应@Recover注解方法,封装到Recovery处理之中,也就是@Retryable和@Recover的自动匹配过程。从构造器可以看出,保存了目标类和目标方法,然后进行解析。

// target为目标类,method为需要Revover的目标方法
public RecoverAnnotationRecoveryHandler(Object target, Method method) {
   this.target = target;
   init(target, method);
}

其核心的init方法代码如下:

private void init(Object target, final Method method) {
//保存传入的方法作为备份方法
    final Map<Class<? extends Throwable>, Method> types = new HashMap();
    //调用ReflectionUtils反射工具,查找符合条件目标方法
    ReflectionUtils.doWithMethods(method.getDeclaringClass(), new MethodCallback() {
    	// 声明回调函数,每个符合条件的目标方法,都会登记到types和methods中
        public void doWith(Method methodx) throws IllegalArgumentException, IllegalAccessException {
        //查找@Recover接口  
            Recover recover = (Recover)AnnotationUtils.findAnnotation(methodx, Recover.class);
            if (recover != null && methodx.getReturnType().isAssignableFrom(method.getReturnType())) {
                Class<?>[] parameterTypes = methodx.getParameterTypes();
                //判断找到的方法和目标方法参数,异常是否一致 
                if (parameterTypes.length > 0 && Throwable.class.isAssignableFrom(parameterTypes[0])) {
                    Class<? extends Throwable> type = parameterTypes[0];
                    //登记下这个revover方法的参数
                    types.put(type, methodx);
                    RecoverAnnotationRecoveryHandler.this.methods.put(methodx, new RecoverAnnotationRecoveryHandler.SimpleMetadata(parameterTypes.length, type));
                } else {
                 	//找不到,就给配置个默认值
                    RecoverAnnotationRecoveryHandler.this.classifier.setDefaultValue(methodx);
                    RecoverAnnotationRecoveryHandler.this.methods.put(methodx, new RecoverAnnotationRecoveryHandler.SimpleMetadata(parameterTypes.length, (Class)null));
                }
            }

        }
    });
    this.classifier.setTypeMap(types);
    this.optionallyFilterMethodsBy(method.getReturnType());
}
9、AnnotationAwareRetryOperationsInterceptor类

​ 注解解析器,查找工程中@Retryable方法,并生成RetryOperationsInterceptor的类。

10、RetryConfiguration类

​ @EnableRetry引入的配置类,内部封装AnnotationClassOrMethodPointcut,AnnotationClassOrMethodFilter,AnnotationMethodsResolver三个Aop工具类。通过反射查找到目标方法,并应用aop给方法加料(生成proxy对象),从而实现把普通方法变成可重试方法。

7、源码执行

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;

   // Allow the retry policy to initialise itself...
   //重试上下文,当前重试次数等记录全在里面
   RetryContext context = open(retryPolicy, state);
   if (this.logger.isTraceEnabled()) {
      this.logger.trace("RetryContext retrieved: " + context);
   }

   // Make sure the context is available globally for clients who need
   // it...
   //注册确保上下文需要context,注册到当前线程中 LocalThread
   RetrySynchronizationManager.register(context);

   Throwable lastException = null;

   boolean exhausted = false;
   try {

      // Give clients a chance to enhance the context...
      拦截器模式,执行RetryListener
      boolean running = doOpenInterceptors(retryCallback, context);

      if (!running) {
         throw new TerminatedRetryException(
               "Retry terminated abnormally by interceptor before first attempt");
      }

      // Get or Start the backoff context...
      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);
         }
      }

      /*
       * We allow the whole loop to be skipped if the policy or context already
       * forbid the first try. This is used in the case of external retry to allow a
       * recovery in handleRetryExhausted without the callback processing (which
       * would throw an exception).
       */
       //判断是否可以重试执行
      while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {

         try {
            if (this.logger.isDebugEnabled()) {
               this.logger.debug("Retry: count=" + context.getRetryCount());
            }
            // Reset the last exception, so if we are successful
            // the close interceptors will not think we failed...
            lastException = null;
            //执行RetryCallback回调
            return retryCallback.doWithRetry(context);
         }
         //异常时,要进行下一次重试准备
         catch (Throwable e) {

            lastException = e;

            try {
            	//遇到异常后,注册该异常的失败次数
               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;
                  // back off was prevented by another thread - fail the retry
                  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);
            }

         }

         /*
          * A stateful attempt that can retry may rethrow the exception before now,
          * but if we get this far in a stateful retry there's a reason for it,
          * like a circuit breaker or a rollback classifier.
          */
           //如果是有状态重试,且有GLOBAL_STATE属性,则立即跳出重试终止;当抛出的异常是非需要执行回滚操作的异常时,才会执行到此处,CircuitBreakerRetryPolicy会在此跳出循环;
         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,则执行此回调,否则抛出异常
      return handleRetryExhausted(recoveryCallback, context, state);

   }
   catch (Throwable e) {
      throw RetryTemplate.<E>wrapIfNecessary(e);
   }
   finally {
      //清理环境
      close(retryPolicy, context, state, lastException == null || exhausted);
      //执行RetryListener#close,比如统计重试信息
	  doCloseInterceptors(retryCallback, context, lastException);
      //清空绑定
      RetrySynchronizationManager.clear();
   }

}

8、熔断源码

熔断器策略配置代码,CircuitBreakerRetryPolic

  • delegate:是真正判断是否重试的策略,当重试失败时,则执行熔断策略;
  • openTimeout:openWindow,配置熔断器电路打开的超时时间,当超过openTimeout之后熔断器电路变成半打开状态(主要有一次重试成功,则闭合电路);
    resetTimeout:timeout,配置重置熔断器重新闭合的超时时间。
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 ((Boolean) getAttribute(CIRCUIT_OPEN) == false) {
            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;
}

从如上代码可看出spring-retry的熔断策略相对简单:

  • 当重试失败,且在熔断器打开时间窗口[0,openWindow) 内,立即熔断;
  • 当重试失败,且在指定超时时间后(>timeout),熔断器电路重新闭合;
  • 在熔断器半打开状态[openWindow, timeout] 时,只要重试成功则重置上下文,断路器闭合。

CircuitBreakerRetryPolicy的delegate应该配置基于次数的SimpleRetryPolicy或者基于超时的TimeoutRetryPolicy策略,且策略都是全局模式,而非局部模式,所以要注意次数或超时的配置合理性。

9、注意

1、使用了@Retryable的方法里面不能使用try…catch包裹,要在方法上抛出异常,不然不会触发。

2、在重试期间这个方法是同步的,如果使用类似Spring Cloud这种框架的熔断机制时,可以结合重试机制来重试后返回结果。

3、@Recover 用于@Retryable重试失败后处理方法,此注解注释的方法参数一定要是@Retryable抛出的异常,否则无法识别,可以在该方法中进行日志处理

4、spring-retry通过AOP实现对目的方法的封装,执行在当前线程下,所以重试过程中当前线程会堵塞。如果BackOff时间设置比较长,最好起异步线程重试(也可以加@Async注解

5、如果使用注解用在幂等性接口上面一定需要设置stateful = true失败就不进行重试,事务回滚

10、结束

整理不易,记得点个赞,你的支持是我最大的动力

11、参考

链接: https://www.jianshu.com/p/58e753ca0151

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值