重试

重试

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

RetryTemplate

从 2.2.0 开始,重试功能已从 Spring Batch 中抽出。 它现在是新库 Spring Retry 的一部分。spring-doc.cn

为了自动化重试操作,Spring Batch 具有该策略。这 以下接口定义 :RetryOperationsRetryOperationsspring-doc.cn

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;

}

基本回调是一个简单的接口,允许您插入一些业务逻辑 retried,如以下接口定义所示:spring-doc.cn

public interface RetryCallback<T, E extends Throwable> {

    T doWithRetry(RetryContext context) throws E;

}

回调运行,如果失败(通过抛出 ),则重试,直到 要么成功,要么实现中止。接口中有许多重载的方法。这些方法处理各种用途 当所有重试尝试都用尽时进行恢复的情况,并处理重试状态,该 允许客户端和实现在调用之间存储信息(我们将在 More 中介绍这一点 详情请见本章)。ExceptionexecuteRetryOperationsspring-doc.cn

最简单的通用实现是 。它 可以按如下方式使用:RetryOperationsRetryTemplatespring-doc.cn

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

RetryContext

的 method 参数是一个 .许多回调会忽略 上下文,但如有必要,它可以用作属性包来存储 iteration 的持续时间。RetryCallbackRetryContextspring-doc.cn

如果 A 中正在进行嵌套重试,则 A 具有父上下文 线。父上下文有时可用于存储需要共享的数据 在对 的调用之间。RetryContextexecutespring-doc.cn

RecoveryCallback

当重试用尽时,可以将控制权传递给不同的回调 称为 .要使用此功能,客户端需要一起传入回调 添加到相同的方法,如以下示例所示:RetryOperationsRecoveryCallbackspring-doc.cn

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

如果在模板决定中止之前业务逻辑未成功,则 客户端有机会通过 Recovery 回调进行一些替代处理。spring-doc.cn

无状态重试

在最简单的情况下,重试只是一个 while 循环。可以保持 尝试,直到成功或失败。contains some state 到 确定是重试还是中止,但此状态在堆栈上,没有必要 将其存储在全球任何位置,因此我们将其称为 Stateless Retry。之间的区别 无状态和有状态重试包含在 (可以同时处理两者) 的实现中。在无状态重试中,重试回调始终为 在失败时所在的同一线程中执行。RetryTemplateRetryContextRetryPolicyRetryTemplatespring-doc.cn

状态重试

如果失败导致事务性资源变得无效,则有一些 特殊注意事项。这不适用于简单的远程调用,因为没有 transactional 资源(通常),但有时它确实适用于数据库更新, 尤其是在使用 Hibernate 时。在这种情况下,只有重新抛出 Exception 立即调用失败,以便事务可以回滚,并且 我们可以开始一个新的、有效的交易。spring-doc.cn

在涉及事务的情况下,无状态重试还不够好,因为 re-throw 和 roll back 必然涉及离开 并可能丢失堆栈上的上下文。为了避免丢失它,我们必须 引入一种存储策略,将其从堆栈中取出并(至少)放入堆中 存储。为此, Spring Batch 提供了一个名为 的存储策略,该策略可以注入到 .默认的 在内存中实现 IS,使用简单的 .高深 在集群环境中与多个进程一起使用时,也可以考虑实现 具有某种集群缓存(但是,即使在集群 环境,这可能有点矫枉过正)。RetryOperations.execute()RetryContextCacheRetryTemplateRetryContextCacheMapRetryContextCachespring-doc.cn

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

识别失败操作的方法是识别多个 重试的调用。要识别状态,用户可以提供一个对象,该对象负责返回标识项目的唯一键。标识符 在界面中用作键。RetryStateRetryContextCachespring-doc.cn

在 和 的实现时要非常小心 返回的键。最好的建议是使用 business key 来识别 项目。对于 JMS 消息,可以使用消息 ID。Object.equals()Object.hashCode()RetryStatespring-doc.cn

当重试用尽时,还可以选择在 以不同的方式调用(现在假定这很可能是 失败)。就像在无状态情况下一样,此选项由 提供,可以通过将其传递给 的方法 来提供。RetryCallbackRecoveryCallbackexecuteRetryOperationsspring-doc.cn

是否重试的决定实际上委托给常规 ,因此 通常对 limits 和 timeout 的担忧可以注入到那里(稍后将对此进行描述 章节)。RetryPolicyspring-doc.cn

重试策略

在 中,在方法中重试或失败的决定是 由 确定,它也是 的工厂。它有责任使用当前策略来创建 ,并在每次尝试时将其传递给 。回调后 失败时,必须调用 以请求它更新其 state(存储在 中),然后询问策略是否再次尝试 可以制作。如果无法进行其他尝试(例如,当达到限制或 timeout),则策略还负责处理 exhausted 状态。 简单实现 throw ,这会导致任何封闭 要回滚的事务。更复杂的 implementations 可能会尝试采用 some recovery 操作,在这种情况下,事务可以保持不变。RetryTemplateexecuteRetryPolicyRetryContextRetryTemplateRetryContextRetryCallbackRetryTemplateRetryPolicyRetryContextRetryExhaustedExceptionspring-doc.cn

失败本质上是可重试的,也可能是不可重试的。如果相同的异常总是 从业务逻辑中抛出,重试它没有好处。所以不要对所有 异常类型。相反,请尝试只关注您预期的那些异常 retryable 的。更积极地重试通常不会对业务逻辑有害,但是 这是浪费的,因为如果失败是确定性的,你会花时间重试某些事情 你提前知道是致命的。spring-doc.cn

Spring Batch 提供了一些简单的 stateless 通用实现,例如 和 (在前面的示例中使用)。RetryPolicySimpleRetryPolicyTimeoutRetryPolicyspring-doc.cn

它允许对任何命名的异常类型列表进行重试,最大为 固定次数。它还列出了永远不应该出现的 “致命” 异常 retried,并且此列表会覆盖 retryable 列表,以便它可以用于提供更精细的 控制重试行为,如以下示例所示:SimpleRetryPolicyspring-doc.cn

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

还有一个更灵活的实现,称为 , 这允许用户为任意一组异常配置不同的重试行为 类型。该策略的工作原理是调用 classifier 将异常转换为 delegate 。例如,一个 异常类型在失败之前可以比另一个类型重试更多次,方法是将其映射到 不同的策略。ExceptionClassifierRetryPolicyExceptionClassifierRetryPolicyspring-doc.cn

用户可能需要实施自己的重试策略,以便做出自定义程度更高的决策。为 实例,当存在众所周知的特定于解决方案的 将异常分类为 Retryable 和 Not Retryable。spring-doc.cn

退避策略

在暂时性故障后重试时,通常片刻后再尝试会有所帮助。 因为通常失败是由一些问题引起的,而这些问题只能通过以下方式解决 等待。如果 a 失败,则可以根据 这。RetryCallbackRetryTemplateBackoffPolicyspring-doc.cn

以下代码显示了接口的接口定义:BackOffPolicyspring-doc.cn

public interface BackoffPolicy {

    BackOffContext start(RetryContext context);

    void backOff(BackOffContext backOffContext)
        throws BackOffInterruptedException;

}

A 可以自由地以它选择的任何方式实现 backOff。策略 由 Spring Batch 提供的开箱即用的 all use .一个常见的用例是 backoff 的等待时间呈指数级增加,以避免两次重试进入 lock step 和 both failed(这是从以太网中吸取的教训)。为此, Spring Batch 提供了 .BackoffPolicyObject.wait()ExponentialBackoffPolicyspring-doc.cn

听众

通常,能够接收跨切关注点的其他回调是很有用的 在许多不同的重试中。为此,Spring Batch 提供了接口。允许用户注册 和 在 迭 代。RetryListenerRetryTemplateRetryListenersRetryContextThrowablespring-doc.cn

以下代码显示了 的接口定义:RetryListenerspring-doc.cn

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

和 回调以最简单的方式出现在整个重试之前和之后 case 的 intent 和 apply 的 single 调用。方法 可能还会收到 .如果出现错误,则它是 这。opencloseonErrorRetryCallbackcloseThrowableRetryCallbackspring-doc.cn

请注意,当有多个侦听器时,它们位于一个列表中,因此有一个 order。 在这种情况下,以相同的顺序调用 while 和 在 倒序。openonErrorclosespring-doc.cn

声明式重试

有时,您知道每次都要重试一些业务处理 发生。这方面的经典示例是远程服务调用。Spring Batch 提供了一个 AOP 拦截器,它将方法调用包装在实现中,仅用于 这个目的。执行拦截的方法并重试 根据中提供的 .RetryOperationsRetryOperationsInterceptorRetryPolicyRepeatTemplatespring-doc.cn

下面的示例展示了一个声明式重试,它使用 Spring AOP 名称空间来 重试对调用的方法的服务调用(有关如何配置 AOP 拦截器,请参见 Spring 用户指南):remoteCallspring-doc.cn

<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 配置重试 service 调用调用的方法(有关如何配置 AOP 的更多详细信息 拦截器,请参见 Spring 用户指南):remoteCallspring-doc.cn

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

前面的示例在拦截器中使用 default。要将 策略或侦听器,则可以将 的实例注入到拦截器中。RetryTemplateRetryTemplatespring-doc.cn