重试

为了使处理更加健壮且不易失败,有时自动重试失败的操作以防它在后续尝试中成功时会有所帮助。易受间歇性故障影响的错误通常是暂时的。DeadlockLoserDataAccessException示例包括对由于网络故障或数据库更新而失败的 Web 服务的远程调用 。

RetryTemplate

自 2.2.0 起,重试功能已从 Spring Batch 中移除。它现在是新库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;

}

基本回调是一个简单的接口,可以让您插入一些要重试的业务逻辑,如下面的接口定义所示:

public interface RetryCallback<T, E extends Throwable> {

    T doWithRetry(RetryContext context) throws E;

}

回调运行,如果它失败(通过抛出一个Exception),它会重试,直到它成功或实现中止。接口中有许多重载 execute方法RetryOperations。当所有重试尝试都用尽时,这些方法处理各种用于恢复的用例,并处理重试状态,这让客户端和实现在调用之间存储信息(我们将在本章后面更详细地介绍这一点)。

最简单的通用实现RetryOperationsRetryTemplate. 它可以按如下方式使用:

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 服务调用并将结果返回给用户。如果该调用失败,则重试,直到达到超时。

RetryContext

的方法参数RetryCallback是 a RetryContext。许多回调忽略上下文,但如有必要,它可以用作属性包来存储迭代期间的数据。

RetryContext如果在同一线程中正在进行嵌套重试,则A具有父上下文。父上下文有时可用于存储需要在调用之间共享的数据execute

RecoveryCallback

当重试用尽时,RetryOperations可以将控制权传递给不同的回调,称为RecoveryCallback. 要使用此功能,客户端将回调一起传递给同一方法,如下例所示:

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
    }
});

如果在模板决定中止之前业务逻辑没有成功,那么客户端就有机会通过恢复回调进行一些替代处理。

无状态重试

在最简单的情况下,重试只是一个 while 循环。可以继续尝试,RetryTemplate直到成功或失败。包含一些状态来决定是重试还是中止,但是这个RetryContext状态是在栈上的,不需要全局存储,所以我们称之为无状态重试。无状态重试和有状态重试的区别包含在实现中RetryPolicyRetryTemplate两者都可以处理)。在无状态重试中,重试回调始终在其失败时所在的同一线程中执行。

有状态重试

在故障导致事务资源无效的情况下,有一些特殊的注意事项。这不适用于简单的远程调用,因为(通常)没有事务性资源,但有时确实适用于数据库更新,尤其是在使用 Hibernate 时。在这种情况下,只有立即重新抛出调用失败的异常才有意义,以便事务可以回滚,我们可以开始一个新的、有效的事务。

在涉及事务的情况下,无状态重试还不够好,因为重新抛出和回滚必然涉及离开RetryOperations.execute()方法并可能丢失堆栈上的上下文。为了避免丢失它,我们必须引入一种存储策略,将其从堆栈中取出并(至少)放入堆存储中。为此,Spring Batch 提供了一种名为 的存储策略 RetryContextCache,可以将其注入到RetryTemplate. 的默认实现RetryContextCache在内存中,使用简单的Map. 在集群环境中使用多个进程的高级用法也可以考虑RetryContextCache使用某种集群缓存来实现(然而,即使在集群环境中,这也可能是矫枉过正)。

的部分责任RetryOperations是在失败的操作在新的执行中返回时识别它们(并且通常包装在新的事务中)。为了促进这一点,Spring Batch 提供了RetryState抽象。execute这与接口中的特殊方法结合使用RetryOperations

识别失败操作的方式是通过多次重试调用识别状态。为了标识状态,用户可以提供一个RetryState 对象,该对象负责返回标识项目的唯一键。标识符用作RetryContextCache界面中的键。

对由. Object.equals()_ 最好的建议是使用业务密钥来识别项目。在 JMS 消息的情况下,可以使用消息 ID。Object.hashCode()RetryState

当重试用尽时,还可以选择以不同的方式处理失败的项目,而不是调用RetryCallback(现在假定它可能会失败)。就像在无状态情况下一样,这个选项是由 提供的 RecoveryCallback,可以通过将其传递给 的execute方法来提供 RetryOperations

重试或不重试的决定实际上是委托给一个常规的RetryPolicy,因此通常对限制和超时的关注可以在那里注入(本章稍后会介绍)。

重试策略

在 a 内部RetryTemplate,方法中重试或失败的execute决定由 a 决定RetryPolicy,它也是 的工厂RetryContext。有 RetryTemplate责任使用当前策略创建一个 RetryContext并将其传递给RetryCallback每次尝试。回调失败后,RetryTemplate必须调用 以RetryPolicy要求其更新其状态(存储在 中RetryContext),然后询问策略是否可以进行另一次尝试。如果无法进行另一次尝试(例如达到限制或检测到超时),则策略还负责处理用尽状态。简单的实现抛出RetryExhaustedException,这会导致任何封闭事务被回滚。更复杂的实现可能会尝试采取一些恢复操作,在这种情况下,事务可以保持不变。

失败本质上是可重试的或不可重试的。如果业务逻辑总是会抛出相同的异常,那么重试它是没有用的。所以不要重试所有异常类型。相反,尝试只关注那些您希望可重试的异常。更积极地重试通常不会对业务逻辑有害,但会造成浪费,因为如果失败是确定性的,那么您会花时间重试事先知道是致命的事情。

Spring Batch 提供了一些简单的 stateless 通用实现 RetryPolicy,例如SimpleRetryPolicyand 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。例如,通过将一种异常类型映射到不同的策略,可以比另一种异常类型在失败前重试更多次。

用户可能需要实施他们自己的重试策略以获得更多自定义决策。例如,当有一个众所周知的、特定于解决方案的异常分类为可重试和不可重试时,自定义重试策略是有意义的。

退避策略

在短暂失败后重试时,在重试之前稍等片刻通常会有所帮助,因为通常失败是由一些只能通过等待才能解决的问题引起的。如果 aRetryCallback失败,则RetryTemplate可以根据BackoffPolicy.

以下代码显示了接口的接口定义BackOffPolicy

public interface BackoffPolicy {

    BackOffContext start(RetryContext context);

    void backOff(BackOffContext backOffContext)
        throws BackOffInterruptedException;

}

ABackoffPolicy可以自由地以它选择的任何方式实现 backOff。Spring Batch 提供的开箱即用策略全部使用Object.wait(). 一个常见的用例是使用呈指数增长的等待时间来回退,以避免两次重试进入锁定步骤并且都失败(这是从以太网中吸取的教训)。为此,Spring Batch 提供了ExponentialBackoffPolicy.

听众

通常,能够在多次不同的重试中接收到针对横切关注点的额外回调是很有用的。为此,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简单的情况下出现在整个重试之前和之后,并onError适用于单个RetryCallback调用。该close方法也可能会收到一个Throwable. 如果出现错误,则为RetryCallback.

请注意,当有多个侦听器时,它们在一个列表中,因此有一个顺序。在这种情况下,open以相同的顺序调用,而onErrorclose相反的顺序调用。

声明式重试

有时,有些业务处理您知道每次发生时都想重试。典型的例子是远程服务调用。Spring Batch 提供了一个 AOP 拦截器,该拦截器将方法调用包装在RetryOperations实现中用于此目的。执行截获的RetryOperationsInterceptor方法并根据RetryPolicy提供的 重试失败RepeatTemplate

以下示例显示了一个声明式重试,它使用 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.retry.interceptor.RetryOperationsInterceptor"/>

下面的例子展示了一个声明式重试,它使用 java 配置来重试对被调用方法的服务调用remoteCall(有关如何配置 AOP 拦截器的更多详细信息,请参阅 Spring 用户指南):

@Bean
public MyService myService() {
	ProxyFactory factory = new ProxyFactory(RepeatOperations.class.getClassLoader());
	factory.setInterfaces(MyService.class);
	factory.setTarget(new MyService());

	MyService service = (MyService) factory.getProxy();
	JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
	pointcut.setPatterns(".*remoteCall.*");

	RetryOperationsInterceptor interceptor = new RetryOperationsInterceptor();

	((Advised) service).addAdvisor(new DefaultPointcutAdvisor(pointcut, interceptor));

	return service;
}

前面的示例RetryTemplate在拦截器中使用了默认值。要更改策略或侦听器,您可以将 的实例RetryTemplate注入拦截器。


1. see XML Configuration