Retry
为了使你的程序更加健壮、减少失败,当你的程序执行失败时,spring Retry 可以帮你自动重试直到执行成功。失败是通常是偶发的。例如远程调用web server 由于网络抖动,或者执行数据库更新时发生了DeadlockLoserDataAccessException
RetryTemplate
- Retry 是从spring Batch 2.2.0 剥离出来的,它现在是一个新的库:Spring Retry
为了自动进行重试Spring Batch 有一个 RetryOperations 策略。下面是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;
}
这个 basic callback 是个简单的接口让你来说实现自己的重试逻辑,定义如下:
public interface RetryCallback<T, E extends Throwable> {
T doWithRetry(RetryContext context) throws E;
}
callback 会在程序异常(通过抛Exception)时执行,重试直至执行成功或达到终止条件。有很多重载方法在RetryOperations 接口中。
最简单的RetryOperations 接口实现是 RetryTemplate,它的用法如下:
RetryTemplate template = new RetryTemplate();
TimeoutRetryPolicy policy = new TimeoutRetryPolicy();
policy.setTimeout(30000L);
template.setRetryPolicy(policy);
Foo result = template.execute(new RetryCallback<Foo>() {
public Foo doWithRetry(RetryContext context) {
// Do stuff that might fail, e.g. webservice operation
return result;
}
});
在前面的实例中,我们调用webservice 将结果返回给用户。如果调用失败,则会重试,直到成功或超时。
RetryContext
RetryCallback 接口方法的参数是RetryContext,很多情况都会忽略这个context,但是如果有必要,这个context可以在重试阶段来存储一些数据。
如果在同一个线程中,发生了嵌套retry,RetryContext有一个父context,父context有时在嵌套retry时父子传数据时会用得到。
RecoveryCallback
当retry次数耗尽时,RetryOperations可以将执行权交给RecoveryCallback的回调,如果要使用这个特性,需要RetryOperations接口的下面这个重载方法:
Foo foo = template.execute(new RetryCallback<Foo>() {
public Foo doWithRetry(RetryContext context) {
// business logic here
},
new RecoveryCallback<Foo>() {
Foo recover(RetryContext context) throws Exception {
// recover logic here
}
});
如果retry次数耗尽还没有重试成功,那么springRetry会执行一次recover callback。
Stateless Retry
最简单的情况,retry等价于一个while循环。 RetryTemplate可以一直尝试,直到成功或失败。RetryContext包含一些状态来决定是重试还是中止,但是这个状态在堆栈上,不需要将它存储在全局的任何地方,所以我们称之为无状态重试(Stateless Retry)。无状态重试和有状态重试之间的区别包含在RetryPolicy的实现中(RetryTemplate可以处理两者)。在无状态重试(Stateless Retry)中,执行失败时,retry callback 方法总是在相同的线程执行。
Stateful Retry
当业务执行失败时,通常会导致事务回滚,这里要考虑一下特殊的情况,常规的数据库操作是可以事务回滚的,但是远程调用通常是无法事务回滚的,只有在数据库事务的情况下,调用失败立即抛出异常进行重试才是有意义的。
在涉及事务的情况下,无状态重试是不够,因为 re-throw 和 roll back 要 在 RetryOperations.execute()方法之外,并可能丢失堆栈上的上下文。为了避免丢失它,我们必须引入一种存储策略,将它从堆栈中取出并(至少)放入堆存储中。为此,Spring Batch提供了一个名为RetryContextCache的存储策略,它可以被注入到RetryTemplate中。
RetryContextCache的默认实现在内存中,使用一个简单的Map。 在集群环境中对多个进程的高级使用还可以考虑使用某种类型的集群缓存实现RetryContextCache(但是,即使在集群环境中,这也可能有点过火)。
RetryOperations的部分职责是,当失败的操作在新的执行中返回时(通常包装在新的事务中)识别它们。 为了方便这一点,Spring Batch提供了RetryState抽象。 这与RetryOperations接口中的特殊执行方法一起工作。
识别失败操作的方法是在重试的多个调用之间识别状态。 要标识状态,用户可以提供一个RetryState对象,该对象负责返回标识该项的唯一键。 该标识符被用作RetryContextCache接口中的一个键。
- 实现RetryState return key 的equals 和 hashCode 方法时要小心,建议使用业务主键来区分他们,在jms message中 通常使用message id
Retry Policies
在RetryTemplate中,重试或执行方法失败的决定是由RetryPolicy决定的,它也是RetryContext的工厂。RetryTemplate有责任使用当前策略创建一个RetryContext,并在每次尝试时将其传递给RetryCallback。回调失败后,RetryTemplate必须调用RetryPolicy,请求它更新其状态(存储在RetryContext中),然后询问策略是否可以再次重试。 如果不能进行下一次重试(例如达到限制或检测到超时),则策略还负责处理exhausted状态。 简单的实现会抛出RetryExhaustedException,这会导致任何封闭事务回滚。 更复杂的实现可能尝试采取一些恢复操作,在这种情况下,事务可以保持不变。
- 失败本要么是可恢复的,要么是不可恢复的。如果你的业务代码逻辑bug导致失败,那么重试是没有必要的。因此,不要对所有异常类型进行重试。 相反,尽量关注那些可以通过重试恢复的异常。更积极地重试通常不会对业务逻辑造成损害,但这是一种浪费,因为如果失败是确定(程序bug)的,那么您将花费时间重试一些您预先知道是致命的。
Spring Batch提供了一些 stateless RetryPolicy的简单通用实现,比如SimpleRetryPolicy和TimeoutRetryPolicy(在前面的示例中使用)。
SimpleRetryPolicy允许对任何指定的一组(list)异常类型,进行重试,重试次数不超过固定的次数。 它还有一个永远不应该重试的“致命”异常列表,这个列表覆盖了可重试列表,以便它可以用于对重试行为进行更精细的控制,如下面的示例所示:
SimpleRetryPolicy policy = new SimpleRetryPolicy();
// Set the max retry attempts
policy.setMaxAttempts(5);
// Retry on all exceptions (this is the default)
policy.setRetryableExceptions(new Class[] {Exception.class});
// ... but never retry IllegalStateException
policy.setFatalExceptions(new Class[] {IllegalStateException.class});
// Use the policy...
RetryTemplate template = new RetryTemplate();
template.setRetryPolicy(policy);
template.execute(new RetryCallback<Foo>() {
public Foo doWithRetry(RetryContext context) {
// business logic here
}
});
Backoff Policies
在retry失败后,通常等待一小段时间后进行下一次重试是有用的,因为失败通常是由某些只能通过等待来解决的问题引起的。 如果RetryCallback失败,RetryTemplate可以根据BackoffPolicy暂停执行。
下面的代码显示了BackOffPolicy接口的接口定义:
public interface BackoffPolicy {
BackOffContext start(RetryContext context);
void backOff(BackOffContext backOffContext)
throws BackOffInterruptedException;
}
BackoffPolicy可以随意实现backOff。 Spring Batch立即提供的策略都使用Object.wait()。 一个常见的用例是使用指数增长的等待时间,以避免两次尝试进入锁定步骤并且都失败(这是从以太网中学到的经验教训)。 为此目的,Spring Batch提供了ExponentialBackoffPolicy。
Spring Batch立即提供的策略都使用Object.wait()。 一个常见的用例是使用指数增长的等待时间,以避免两次尝试进入锁定步骤并且都失败(这是从以太网中学到的经验教训)。 为此目的,Spring Batch提供了ExponentialBackoffPolicy。
Listeners
通常,能够在不同的重试中为横切关注点接收额外的回调是有用的。 为此,Spring Batch提供了RetryListener接口。 RetryTemplate允许用户注册RetryListeners,并且在迭代期间为他们提供带有RetryContext和Throwable的回调。
下面的代码显示了RetryListener的接口定义:
public interface RetryListener {
<T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback);
<T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable);
<T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable);
}
在最简单的情况下,open 和 close callbacks 分别在重试之前和重试之后执行,onError应用于单个RetryCallback调用。 close方法也可以接收Throwable。 如果有错误,它是由RetryCallback抛出的最后一个错误。
请注意,当有多个侦听器时,它们在一个列表中,因此有一个顺序。 在这种情况下,open按相同的顺序调用,而onError和close按相反的顺序调用。