1、介绍
spring retry是从spring batch独立出来的一个能功能,主要实现了重试和熔断,对于重试是有场景限制的,不是什么场景都适合重试, 比如参数校验不合法、写操作等(要考虑写是否幂等)都不适合重试。 比如外部 RPC 调用,或者数据入库等操作,如果一次操作失败,可以进行多次重试,提高调用成功的可能性。
2、框架介绍
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,重试上下文的具体实现。
- 扩展AttributeAccessorSupport:内部有个linkedHashMap存储标准属性外的其他属性
- 有parent属性,在构造时候传入其父上下文,这样就维护了一个链表信息,方便后续查找。
- 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、结束
整理不易,记得点个赞,你的支持是我最大的动力