Spring(34)——Spring Retry介绍
Posted elim168
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring(34)——Spring Retry介绍相关的知识,希望对你有一定的参考价值。
Spring Retry介绍
Spring retry是Spring提供的一种重试机制的解决方案。它内部抽象了一个RetryOperations接口,其定义如下。
public interface RetryOperations
<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;
从定义中可以看到它定义了几个重载的execute()
,它们之间的差别就在于RetryCallback、RecoveryCallback、RetryState,其中核心参数是RetryCallback。RetryCallback的定义如下,从定义中可以看到它就定义了一个doWithRetry()
,该方法的返回值就是RetryOperations的execute()
的返回值,RetryCallback的范型参数中定义的Throwable是中执行可重试方法时可抛出的异常,可由外部进行捕获。
public interface RetryCallback<T, E extends Throwable>
T doWithRetry(RetryContext context) throws E;
当RetryCallback不能再重试的时候,如果定义了RecoveryCallback,就会调用RecoveryCallback,并以其返回结果作为execute()
的返回结果。其定义如下。RetryCallback和RecoverCallback定义的接口方法都可以接收一个RetryContext参数,通过它可以获取到尝试次数,也可以通过其setAttribute()
和getAttribute()
来传递一些信息。
public interface RecoveryCallback<T>
T recover(RetryContext context) throws Exception;
Spring Retry包括有状态的重试和无状态的重试,对于有状态的重试,它主要用来提供一个用于在RetryContextCache中保存RetryContext的Key,这样可以在多次不同的调用中应用同一个RetryContext(无状态的重试每次发起调用都是一个全新的RetryContext,在整个重试过程中是一个RetryContext,其不会进行保存。有状态的重试因为RetryContext是保存的,其可以跨或不跨线程在多次execute()
调用中应用同一个RetryContext)。
使用Spring Retry需要引入如下依赖。
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.2.4.RELEASE</version>
</dependency>
Spring Retry提供了一个RetryOperations的实现,RetryTemplate,通过它我们可以发起一些可重试的请求。其内部的重试机制通过RetryPolicy来控制。RetryTemplate默认使用的是SimpleRetryPolicy实现,SimpleRetryPolicy只是简单的控制尝试几次,包括第一次调用。RetryTemplate默认使用的是尝试3次的策略。所以下面的单元策略是可以通过的,第一二次尝试都失败,此时counter变为3了,第3次尝试成功了,counter变为4了。
@Test
public void test()
RetryTemplate retryTemplate = new RetryTemplate();
AtomicInteger counter = new AtomicInteger();
RetryCallback<Integer, IllegalStateException> retryCallback = retryContext ->
if (counter.incrementAndGet() < 3) //内部默认重试策略是最多尝试3次,即最多重试两次。
throw new IllegalStateException();
return counter.incrementAndGet();
;
Integer result = retryTemplate.execute(retryCallback);
Assert.assertEquals(4, result.intValue());
接着来看一个使用RecoveryCallback的例子。我们把上面的例子简单改了下,改为调用包含RecoveryCallback入参的execute()
,RetryCallback内部也改为了即使尝试了3次后仍然会失败。此时将转为调用RecoveryCallback,RecoveryCallback内部通过RetryContext获取了尝试次数,此时RetryCallback已经尝试3次了,所以RetryContext获取的尝试次数是3,RecoveryCallback的返回结果30将作为execute()
的返回结果。
@Test
public void testRecoveryCallback()
RetryTemplate retryTemplate = new RetryTemplate();
AtomicInteger counter = new AtomicInteger();
RetryCallback<Integer, IllegalStateException> retryCallback = retryContext ->
//内部默认重试策略是最多尝试3次,即最多重试两次。还不成功就会抛出异常。
if (counter.incrementAndGet() < 10)
throw new IllegalStateException();
return counter.incrementAndGet();
;
RecoveryCallback<Integer> recoveryCallback = retryContext ->
//返回的应该是30。RetryContext.getRetryCount()记录的是尝试的次数,一共尝试了3次。
return retryContext.getRetryCount() * 10;
;
//尝试策略已经不满足了,将不再尝试的时候会抛出异常。此时如果指定了RecoveryCallback将执行RecoveryCallback,
//然后获得返回值。
Integer result = retryTemplate.execute(retryCallback, recoveryCallback);
Assert.assertEquals(30, result.intValue());
RetryPolicy
RetryTemplate内部的重试策略是由RetryPolicy控制的。RetryPolicy的定义如下。
public interface RetryPolicy extends Serializable
/**
* @param context the current retry status
* @return true if the operation can proceed
*/
boolean canRetry(RetryContext context);
/**
* Acquire resources needed for the retry operation. The callback is passed
* in so that marker interfaces can be used and a manager can collaborate
* with the callback to set up some state in the status token.
* @param parent the parent context if we are in a nested retry.
*
* @return a @link RetryContext object specific to this policy.
*
*/
RetryContext open(RetryContext parent);
/**
* @param context a retry status created by the
* @link #open(RetryContext) method of this policy.
*/
void close(RetryContext context);
/**
* Called once per retry attempt, after the callback fails.
*
* @param context the current status object.
* @param throwable the exception to throw
*/
void registerThrowable(RetryContext context, Throwable throwable);
SimpleRetryPolicy
RetryTemplate内部默认时候用的是SimpleRetryPolicy。SimpleRetryPolicy默认将对所有异常进行尝试,最多尝试3次。如果需要调整使用的RetryPolicy,可以通过RetryTemplate的setRetryPolicy()
进行设置。比如下面代码就显示的设置了需要使用的RetryPolicy是不带参数的SimpleRetryPolicy,其默认会尝试3次。
public void testSimpleRetryPolicy()
RetryPolicy retryPolicy = new SimpleRetryPolicy();
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(retryPolicy);
AtomicInteger counter = new AtomicInteger();
Integer result = retryTemplate.execute(retryContext ->
if (counter.incrementAndGet() < 3)
throw new IllegalStateException();
return counter.get();
);
Assert.assertEquals(3, result.intValue());
如果希望最多尝试10次,只需要传入构造参数10即可,比如下面这样。
RetryPolicy retryPolicy = new SimpleRetryPolicy(10);
在实际使用的过程中,可能你不会希望所有的异常都进行重试,因为有的异常重试是解决不了问题的。所以可能你会想要指定可以重试的异常类型。通过SimpleRetryPolicy的构造参数可以指定哪些异常是可以进行重试的。比如下面代码我们指定了最多尝试10次,且只有IllegalStateException是可以进行重试的。那么在运行下面代码时前三次抛出的IllegalStateException都会再次进行尝试,第四次会抛出IllegalArgumentException,此时不能继续尝试了,该异常将会对外抛出。
@Test
public void testSimpleRetryPolicy()
Map<Class<? extends Throwable>, Boolean> retryableExceptions = Maps.newHashMap();
retryableExceptions.put(IllegalStateException.class, true);
RetryPolicy retryPolicy = new SimpleRetryPolicy(10, retryableExceptions);
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(retryPolicy);
AtomicInteger counter = new AtomicInteger();
retryTemplate.execute(retryContext ->
if (counter.incrementAndGet() < 3)
throw new IllegalStateException();
else if (counter.incrementAndGet() < 6)
throw new IllegalArgumentException();
return counter.get();
);
看到这里可能你会有疑问,可以进行重试的异常定义为什么使用的是Map结构,而不是简单的通过Set或List来定义可重试的所有异常类似,而要多一个Boolean类型的Value来定义该异常是否可重试。这样做的好处是它可以实现包含/排除的逻辑,比如下面这样,我们可以指定对所有的RuntimeException都是可重试的,唯独IllegalArgumentException是一个例外。所以当你运行如下代码时其最终结果还是抛出IllegalArgumentException。
@Test
public void testSimpleRetryPolicy()
Map<Class<? extends Throwable>, Boolean> retryableExceptions = Maps.newHashMap();
retryableExceptions.put(RuntimeException.class, true);
retryableExceptions.put(IllegalArgumentException.class, false);
RetryPolicy retryPolicy = new SimpleRetryPolicy(10, retryableExceptions);
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(retryPolicy);
AtomicInteger counter = new AtomicInteger();
retryTemplate.execute(retryContext ->
if (counter.incrementAndGet() < 3)
throw new IllegalStateException();
else if (counter.incrementAndGet() < 6)
throw new IllegalArgumentException();
return counter.get();
);
SimpleRetryPolicy在判断一个异常是否可重试时,默认会取最后一个抛出的异常。我们通常可能在不同的业务层面包装不同的异常,比如有些场景我们可能需要把捕获到的异常都包装为BusinessException,比如说把一个IllegalStateException包装为BusinessException。我们程序中定义了所有的IllegalStateException是可以进行重试的,如果SimpleRetryPolicy直接取的最后一个抛出的异常会取到BusinessException。这可能不是我们想要的,此时可以通过构造参数traverseCauses指定可以遍历异常栈上的每一个异常进行判断。比如下面代码,在traverseCauses=false
时,只会在抛出IllegalStateException时尝试3次,第四次抛出的Exception不是RuntimeException,所以不会进行重试。指定了traverseCauses=true
时第四次尝试时抛出的Exception,再往上找时会找到IllegalArgumentException,此时又可以继续尝试,所以最终执行后counter的值会是6。
@Test
public void testSimpleRetryPolicy() throws Exception
Map<Class<? extends Throwable>, Boolean> retryableExceptions = Maps.newHashMap();
retryableExceptions.put(RuntimeException.class, true);
RetryPolicy retryPolicy = new SimpleRetryPolicy(10, retryableExceptions, true);
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(retryPolicy);
AtomicInteger counter = new AtomicInteger();
retryTemplate.execute(retryContext ->
if (counter.incrementAndGet() < 3)
throw new IllegalStateException();
else if (counter.incrementAndGet() < 6)
try
throw new IllegalArgumentException();
catch (Exception e)
throw new Exception(e);
return counter.get();
);
SimpleRetryPolicy除了前面介绍的3个构造方法外,还有如下这样一个构造方法,它的第四个参数表示当抛出的异常是在retryableExceptions中没有定义是否需要尝试时其默认的值,该值为true则表示默认可尝试。
public SimpleRetryPolicy(int maxAttempts, Map<Class<? extends Throwable>, Boolean> retryableExceptions,
boolean traverseCauses, boolean defaultValue)
下面代码中通过retryableExceptions指定了抛出IllegalFormatException时不进行重试,然后通过SimpleRetryPolicy的第四个参数指定了其它异常默认是可以进行重试的。所以下面的代码也可以正常运行,运行结束后counter的值是6。
@Test
public void testSimpleRetryPolicy() throws Exception
Map<Class<? extends Throwable>, Boolean> retryableExceptions = Maps.newHashMap();
retryableExceptions.put(IllegalFormatException.class, false);
RetryPolicy retryPolicy = new SimpleRetryPolicy(10, retryableExceptions, false, true);
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(retryPolicy);
AtomicInteger counter = new AtomicInteger();
retryTemplate.execute(retryContext ->
if (counter.incrementAndGet() < 3)
throw new IllegalStateException();
else if (counter.incrementAndGet() < 6)
throw new IllegalArgumentException();
return counter.get();
);
AlwaysRetryPolicy
顾名思义就是一直重试,直到成功为止。所以对于下面代码而言,其会一直尝试100次,第100次的时候它就成功了。
@Test
public void testRetryPolicy()
RetryPolicy retryPolicy = new AlwaysRetryPolicy();
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(retryPolicy);
AtomicInteger counter = new AtomicInteger();
Integer result = retryTemplate.execute(retryContext ->
if (counter.incrementAndGet() < 100)
throw new IllegalStateException();
return counter.get();
);
Assert.assertEquals(100, result.intValue());
NeverRetryPolicy
与AlwaysRetryPolicy相对的一个极端是从不重试,NeverRetryPolicy的策略就是从不重试,但是第一次调用还是会发生的。所以对于下面代码而言,如果第一次获取的随机数不是3的倍数,则可以正常执行,否则将抛出IllegalStateException。
@Test
public void testRetryPolicy()
RetryPolicy retryPolicy = new NeverRetryPolicy();
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(retryPolicy);
retryTemplate.execute(retryContext ->
int value = new Random().nextInt(100);
if (value % 3 == 0)
throw new IllegalStateException();
return value;
);
TimeoutRetryPolicy
TimeoutRetryPolicy用于在指定时间范围内进行重试,直到超时为止,默认的超时时间是1000毫秒。
@Test
public void testRetryPolicy() throws Exception
TimeoutRetryPolicy retryPolicy = new TimeoutRetryPolicy();
retryPolicy.setTimeout(2000);//不指定时默认是1000
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(retryPolicy);
AtomicInteger counter = new AtomicInteger();
Integer result = retryTemplate.execute(retryContext ->
if (counter.incrementAndGet() < 10)
TimeUnit.MILLISECONDS.sleep(20);
throw new IllegalStateException();
return counter.get();
);
Assert.assertEquals(10, result.intValue());
ExceptionClassifierRetryPolicy
之前介绍的SimpleRetryPolicy可以基于异常来判断是否需要进行重试。如果你需要基于不同的异常应用不同的重试策略怎么办呢?ExceptionClassifierRetryPolicy可以帮你实现这样的需求。下面的代码中我们就指定了当捕获的是IllegalStateException时将最多尝试5次,当捕获的是IllegalArgumentException时将最多尝试4次。其执行结果最终是抛出IllegalArgumentException的,但是在最终抛出IllegalArgumentException时counter的值是多少呢?换句话说它一共尝试了几次呢?答案是8次。按照笔者的写法,进行第5次尝试时不会抛出IllegalStateException,而是抛出IllegalArgumentException,它对于IllegalArgumentException的重试策略而言是第一次尝试,之后会再尝试3次,5+3=8,所以counter的最终的值是8。
@Test
public void testRetryPolicy() throws Exception
ExceptionClassifierRetryPolicy retryPolicy = new ExceptionClassifierRetryPolicy();
Map<Class<? extends Throwable>, RetryPolicy> policyMap = Maps.newHashMap();
policyMap.put(IllegalStateException.class, new SimpleRetryPolicy(5));
policyMap.put(IllegalArgumentException.class, new SimpleRetryPolicy(4));
retryPolicy.setPolicyMap(policyMap);
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(retryPolicy);
AtomicInteger counter = new AtomicInteger();
retryTemplate.execute(retryContext ->
if (counter.incrementAndGet() < 5)
throw new IllegalStateException();
else if (counter.get() < 10)
throw new IllegalArgumentExceptionspring-retry实现方法请求重试