On this page
9. Retry
9.1 RetryTemplate
Note
从 2.2.0 版本开始,重试功能已从 Spring Batch 中撤出。现在它是新库 Spring Retry 的一部分。
为了使处理过程更健壮且更不容易出错,有时可以自动重试失败的操作,以防后续尝试成功执行。易受此类处理影响的错误本质上是暂时的。例如,由于网络故障或数据库更新中的DeadLockLoserException而导致对 Web 服务或 RMI 服务的远程调用失败,可能会在短暂的 await 后解决。为了使此类操作自动重试,Spring Batch 具有RetryOperations策略。 RetryOperations界面如下所示:
public interface RetryOperations {
<T> T execute(RetryCallback<T> retryCallback) throws Exception;
<T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback)
throws Exception;
<T> T execute(RetryCallback<T> retryCallback, RetryState retryState)
throws Exception, ExhaustedRetryException;
<T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback,
RetryState retryState) throws Exception;
}
基本回调是一个简单的接口,允许您插入一些要重试的业务逻辑:
public interface RetryCallback<T> {
T doWithRetry(RetryContext context) throws Throwable;
}
回调将被执行,如果失败(通过抛出Exception),则将重试该回调,直到该回调成功或实现决定中止为止。 RetryOperations接口中有许多重载的execute方法,它们处理所有重试尝试都已用尽时用于恢复的各种用例,并且还具有重试状态,这使 Client 端和实现可以在调用之间存储信息(稍后会详细介绍)。
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;
}
});
在该示例中,我们执行 Web 服务调用并将结果返回给用户。如果该呼叫失败,则重试直到超时。
9.1.1 RetryContext
RetryCallback的方法参数是RetryContext。许多回调将仅忽略上下文,但如有必要,可以将其用作属性包,以在迭代过程中存储数据。
如果在同一线程中正在进行嵌套重试,则RetryContext将具有父上下文。父上下文有时对于存储需要在execute的调用之间共享的数据很有用。
9.1.2 RecoveryCallback
重试完成后,RetryOperations可以将控制权传递给另一个回调RecoveryCallback。要使用此功能,Client 端只需将回调一起传递给相同的方法,例如:
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
}
});
如果在模板决定中止之前业务逻辑未成功,则将使 Client 端有机会通过恢复回调进行一些替代处理。
9.1.3Stateless 重试
在最简单的情况下,重试只是一个 while 循环:RetryTemplate可以 continue 尝试直到成功或失败为止。 RetryContext包含一些状态来确定是重试还是中止,但是此状态在堆栈上,不需要将其全局存储在任何地方,因此我们将其称为 Stateless 重试。Stateless 重试和有状态重试之间的区别包含在RetryPolicy的实现中(RetryTemplate可以同时处理两者)。在 Stateless 重试中,回调总是在失败时在相同的线程中执行。
9.1.4 有状态重试
故障导致事务资源无效的地方,有一些特殊的注意事项。因为通常没有事务资源,所以这不适用于简单的远程调用,但是有时确实适用于数据库更新,尤其是在使用 Hibernate 时。在这种情况下,有意义的是立即重新抛出调用失败的异常,以便事务可以回滚并且我们可以开始一个新的有效事务。
在这些情况下,Stateless 重试不够好,因为重新抛出和回滚必然涉及离开RetryOperations.execute()方法并可能丢失堆栈中的上下文。为了避免丢失它,我们必须引入一种存储策略以将其从堆栈中取出并(至少)放入堆存储中。为此,Spring Batch 提供了一种存储策略RetryContextCache,可以将其注入RetryTemplate。 RetryContextCache的默认实现在内存中,使用简单的Map。在群集环境中对多个进程进行高级使用时,可能还会考虑使用某种类型的群集缓存来实现RetryContextCache(尽管,即使在群集环境中,这也可能会过大)。
RetryOperations的部分职责是识别失败的操作,使其在新的执行中恢复(通常包含在新的事务中)。为方便起见,Spring Batch 提供了RetryState抽象。这与RetryOperations中的特殊execute方法结合使用。
识别失败操作的方式是通过在重试的多次调用中识别状态。为了标识状态,用户可以提供一个RetryState对象,该对象负责返回标识该 Item 的唯一键。标识符用作RetryContextCache中的键。
Warning
在RetryState返回的键中对Object.equals()和Object.hashCode()的实现要非常小心。最好的建议是使用业务密钥来识别 Item。对于 JMS 消息,可以使用消息 ID。
重试用尽后,还可以选择以其他方式处理失败的 Item,而不是调用RetryCallback(现在假定它很可能失败)。就像在 Stateless 情况下一样,此选项由RecoveryCallback提供,可以通过将其传递给RetryOperations的execute方法来提供。
是否重试的决定实际上是委托给常规的RetryPolicy,因此可以将有关限制和超时的常见问题注入那里(请参阅下文)。
9.2 重试策略
在RetryTemplate内部,由RetryPolicy决定execute方法重试还是失败的决定,该RetryPolicy也是RetryContext的工厂。 RetryTemplate负责使用当前策略创建RetryContext,并在每次尝试时将其传递给RetryCallback。回调失败后,RetryTemplate必须调用RetryPolicy以要求其更新其状态(将存储在RetryContext中),然后询问策略是否可以进行另一次尝试。如果无法进行其他尝试(例如,达到限制或检测到超时),则该策略还负责处理耗尽状态。简单的实现只会抛出RetryExhaustedException,这将导致任何封闭的事务被回滚。更复杂的实现可能会尝试采取一些恢复操作,在这种情况下,事务可以保持不变。
Tip
故障本质上是可以重试的,还是不能重试的-如果总是要从业务逻辑中引发相同的异常,则重试将无济于事。因此,请勿重试所有异常类型-尝试仅关注您希望可重试的那些异常。主动重试通常对业务逻辑没有害处,但是这样做是浪费的,因为如果故障是确定性的,那么将有时间花费在重试事先知道是致命的事情上。
Spring Batch 提供了一些 Stateless 的RetryPolicy的简单通用实现,例如SimpleRetryPolicy和上面示例中使用的TimeoutRetryPolicy。
SimpleRetryPolicy仅允许重试任何命名类型的异常类型,最多可重复固定次数。它还有一个“致命”异常列表,这些列表永远都不应重试,并且此列表会覆盖可重试列表,以便可以使用它来更好地控制重试行为:
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
}
});
还有一种更灵活的实现,称为ExceptionClassifierRetryPolicy,它允许用户通过ExceptionClassifier抽象为任意一组异常类型配置不同的重试行为。该策略通过调用分类器将异常转换为委托RetryPolicy来起作用,例如,与通过将异常 Map 到其他策略相比,可以在失败之前重试一个异常类型多次。
用户可能需要实施自己的重试策略以进行更多自定义的决策。例如,如果存在众所周知的,特定于解决方案的 exception,则将异常分类为可重试和不可重试。
9.3 退避 Policy
在短暂故障后重试时,通常有助于稍等一会再尝试,因为通常故障是由某些问题引起的,只有通过 await 才能解决。如果RetryCallback失败,则RetryTemplate可以根据适当的BackoffPolicy暂停执行。
public interface BackoffPolicy {
BackOffContext start(RetryContext context);
void backOff(BackOffContext backOffContext)
throws BackOffInterruptedException;
}
BackoffPolicy可以自由选择以任何方式实施 backOff。 Spring Batch 提供的现成策略都使用Object.wait()。一个常见的用例是使用成倍增加的 await 时间来回退,以避免两次重试进入锁定步骤而都失败了-这是从以太网中学到的教训。为此,Spring Batch 提供了ExponentialBackoffPolicy。
9.4 Listeners
通常,能够收到更多的回调,以解决许多不同的重试中的交叉问题。为此,Spring Batch 提供了RetryListener接口。 RetryTemplate允许用户注册RetryListener,并且将在迭代过程中向他们提供RetryContext和Throwable的回调。
界面如下所示:
public interface RetryListener {
void open(RetryContext context, RetryCallback<T> callback);
void onError(RetryContext context, RetryCallback<T> callback, Throwable e);
void close(RetryContext context, RetryCallback<T> callback, Throwable e);
}
在最简单的情况下,open和close回调在整个重试之前和之后出现,并且onError适用于单个RetryCallback调用。 close方法可能还会收到Throwable;如果有错误,则是RetryCallback抛出的最后一个错误。
请注意,当有多个侦听器时,它们在列表中,因此有一个 Sequences。在这种情况下,open的调用 Sequences 相同,而onError和close的调用 Sequences 相反。
9.5 声明式重试
有时,您知道某些业务处理每次发生时都想重试。典型的例子是远程服务调用。 Spring Batch 为此提供了一个 AOP 拦截器,该方法将方法调用包装在RetryOperations中。 RetryOperationsInterceptor根据提供的RepeatTemplate中的RetryPolicy执行拦截的方法并重试失败。
这是一个使用 Spring AOP 名称空间重复声明称为remoteCall的服务的声明式迭代的示例(有关如何配置 AOP 拦截器的更多详细信息,请参见 Spring 用户指南):
<aop:config>
<aop:pointcut id="transactional"
expression="execution(* com..*Service.remoteCall(..))" />
<aop:advisor pointcut-ref="transactional"
advice-ref="retryAdvice" order="-1"/>
</aop:config>
<bean id="retryAdvice"
class="org.springframework.batch.retry.interceptor.RetryOperationsInterceptor"/>
上面的示例在拦截器内部使用默认的RetryTemplate。要更改策略或侦听器,您只需要将RetryTemplate的实例注入到拦截器中。