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
的实例注入到拦截器中。