spring的retry机制

为了使处理更健壮并更不易失败,有时自动重试失败的操作会有所帮助,以防后续尝试可能成功。容易受到间歇性故障影响的错误通常在本质上是短暂的。示例包括由于网络故障或数据库更新中的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;

}

回调将运行,如果它失败(通过抛出异常),则会重试它,直到成功或实现中止。在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;
    }

});

在前面的示例中,我们进行web服务调用并将结果返回给用户。如果该调用失败,则重试该调用,直到达到超时。

RetryContext

RetryCallback的方法参数是RetryContext。许多回调忽略上下文,但是,如果有必要,它可以作为一个属性包来存储迭代期间的数据。
如果在同一个线程中有一个嵌套的重试正在进行,则RetryContext具有父上下文。父上下文有时用于存储需要在执行的调用之间共享的数据。

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

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

Stateless Retry (无状态重试)

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

Stateful Retry(有状态重试)

当失败导致事务资源无效时,需要考虑一些特殊的事项。这并不适用于简单的远程调用,因为没有事务性资源(通常),但有时适用于数据库更新,特别是在使用Hibernate时。在这种情况下,只需要立即重新抛出调用失败的异常,这样事务就可以回滚,我们就可以启动一个新的、有效的事务。

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

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

识别失败操作的方法是跨多个重试调用识别状态。为了标识状态,用户可以提供一个RetryState对象,该对象负责返回标识项目的唯一键。标识符在RetryContextCache接口中用作键。

要非常小心RetryState返回的键中Object.equals()和Object.hashCode()的实现。最好的建议是使用业务键来标识项目。对于JMS消息,可以使用消息ID。

当重试耗尽时,还可以选择以另一种方式处理失败的项,而不是调用RetryCallback(现在认为很可能失败)。就像在无状态的情况下一样,这个选项是由RecoveryCallback提供的,它可以通过将它传递到RetryOperations的execute方法来提供。
是否重试的决定实际上被委托给了一个常规的RetryPolicy,所以通常关于限制和超时的问题可以在那里注入(在本章后面描述)。

Retry Policies (重试策略)

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

失败在本质上要么是可重试的,要么是不可重试的。如果总是从业务逻辑抛出相同的异常,那么重试它是没有用的。因此,不要对所有异常类型进行重试。相反,试着只关注那些您期望可重试的异常。更积极地重试通常不会对业务逻辑造成伤害,但这是一种浪费,因为如果失败是确定的,那么您将花费时间重新尝试预先知道是致命的东西。

Spring Batch提供了一些简单的无状态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来工作。例如,通过将一种异常类型映射到不同的策略,可以在失败前比另一种异常类型重试更多次。
用户可能需要实现自己的重试策略,以实现更多的自定义决策。例如,当存在一个众所周知的、特定于解决方案的异常分类为可重试和不可重试时,定制的重试策略是有意义的。

Backoff Policies(回滚策略)

在出现短暂故障后重新尝试时,在再次尝试之前稍等一下通常是有帮助的,因为通常故障是由一些只有等待才能解决的问题引起的。如果RetryCallback失败,RetryTemplate可以根据BackoffPolicy暂停执行。
下面的代码显示了BackOffPolicy接口的接口定义:

public interface BackoffPolicy {

    BackOffContext start(RetryContext context);

    void backOff(BackOffContext backOffContext)
        throws BackOffInterruptedException;

}

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

Listeners(监听器)

通常,能够通过许多不同的重试来接收横切关注点的额外回调是很有用的。为此,Spring Batch提供了RetryListener接口。RetryTemplate允许用户注册retrylistener,并在迭代期间提供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方法也可以接收一个抛出对象。如果有错误,它是RetryCallback抛出的最后一个错误。
请注意,当有多个侦听器时,它们在一个列表中,因此有一个顺序。在这种情况下,open的调用顺序相同,而onError和close的调用顺序相反。

Declarative Retry (声明式)

有时,您知道有些业务处理每次发生时都需要重试。这方面的典型例子是远程服务调用。Spring Batch提供了一个AOP拦截器,用于在RetryOperations实现中包装方法调用。RetryOperationsInterceptor执行被拦截的方法,并根据提供的reretry plate中的RetryPolicy在失败时重试。
下面的例子展示了一个声明性的重试,它使用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的实例注入拦截器。

参考:spring 官方文档

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容