Spring Retry

这篇具有很好参考价值的文章主要介绍了Spring Retry。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

工作中,经常遇到需要重试的场景,最简单的方式可以用try...catch...加while循环来实现。那么,有没有统一的、优雅一点儿的处理方式呢?有的,Spring Retry就可以帮我们搞定重试问题。

关于重试,我们可以关注以下以下几个方面:

  • 什么情况下去触发重试机制
  • 重试多少次,重试的时间间隔
  • 是否可以对重试过程进行监视

接下来,带着这些思考,一起看下Spring Retry是如何解决这些问题的

首先,引入依赖。

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

有两种使用方式:命令式和声明式

1. 命令式

RetryTemplate template = RetryTemplate.builder()
        .maxAttempts(3)
        .fixedBackoff(1000)
        .retryOn(RemoteAccessException.class)
        .build();

template.execute(ctx -> {
    // ... do something
});

命令式主要是利用RetryTemplate。RetryTemplate 实现了 RetryOperations 接口。

RetryTemplate template = new RetryTemplate();

TimeoutRetryPolicy policy = new TimeoutRetryPolicy();
policy.setTimeout(30000L);  //  30秒内可以重试,超过30秒不再重试

template.setRetryPolicy(policy);

MyObject result = template.execute(new RetryCallback<MyObject, Exception>() {

    public MyObject doWithRetry(RetryContext context) {
        // Do stuff that might fail, e.g. webservice operation
        return result;
    }

});

RetryTemplate 也支持流式配置

//  最大重试10次,第一次间隔100ms,第二次200ms,第三次400ms,以此类推,最大间隔10000ms
RetryTemplate.builder()
      .maxAttempts(10)
      .exponentialBackoff(100, 2, 10000)
      .retryOn(IOException.class)
      .traversingCauses()
      .build();

//  3秒内可以一直重试,每次间隔10毫秒,3秒以后就不再重试了
RetryTemplate.builder()
      .fixedBackoff(10)
      .withinMillis(3000)
      .build();

//  无限重试,间隔最小1秒,最大3秒
RetryTemplate.builder()
      .infiniteRetry()
      .retryOn(IOException.class)
      .uniformRandomBackoff(1000, 3000)
      .build();

当重试耗尽时,RetryOperations可以将控制传递给另一个回调:RecoveryCallback

template.execute(new RetryCallback<Object, Throwable>() {
    @Override
    public Object doWithRetry(RetryContext context) throws Throwable {
        // 业务逻辑
        return null;
    }
}, new RecoveryCallback<Object>() {
    @Override
    public Object recover(RetryContext context) throws Exception {
        //  恢复逻辑
        return null;
    }
});

如果重试次数耗尽时,业务逻辑还没有执行成功,那么执行恢复逻辑来进行兜底处理(兜底方案)

无状态的重试

在最简单的情况下,重试只是一个while循环:RetryTemplate可以一直尝试,直到成功或失败。RetryContext包含一些状态,用于确定是重试还是中止。然而,这个状态是在堆栈上的,不需要在全局的任何地方存储它。因此,我们称之为“无状态重试”。无状态重试和有状态重试之间的区别包含在RetryPolicy的实现中。在无状态重试中,回调总是在重试失败时的同一个线程中执行。

有状态的重试

如果故障导致事务性资源失效,则需要考虑一些特殊问题。这并不适用于简单的远程调用,因为(通常)没有事务性资源,但它有时适用于数据库更新,特别是在使用Hibernate时。在这种情况下,只有重新抛出立即调用失败的异常才有意义,这样事务才能回滚,我们才能开始一个新的(有效的)事务。在这些情况下,无状态重试还不够好,因为重新抛出和回滚必然涉及离开RetryOperations.execute()方法,并且可能丢失堆栈上的上下文。为了避免丢失上下文,我们必须引入一种存储策略,将其从堆栈中取出,并(至少)将其放入堆存储中。为此,Spring Retry提供了一个名为RetryContextCache的存储策略,您可以将其注入到RetryTemplate中。RetryContextCache的默认实现是在内存中,使用一个简单的Map。它具有严格强制的最大容量,以避免内存泄漏,但它没有任何高级缓存特性(例如生存时间)。如果需要,你应该考虑注入具有这些特性的Map。

重试策略

在RetryTemplate中,由RetryPolicy决定是重试还是失败。RetryTemplate负责使用当前策略创建RetryContext,并在每次重试时将其传递给RetryCallback。回调失败后,RetryTemplate必须调用RetryPolicy,要求它更新自己的状态(存储在RetryContext中)。然后询问政策是否可以再尝试一次。如果不能进行另一次重试(例如,因为已达到限制或检测到超时),策略还负责标识耗尽状态——但不负责处理异常。当没有恢复可用时,RetryTemplate抛出原始异常,但有状态情况除外。在这种情况下,它会抛出RetryExhaustedException。还可以在RetryTemplate中设置一个标志,让它无条件地抛出回调(即用户代码)中的原始异常。

// Set the max attempts including the initial attempt before retrying
// and retry on all exceptions (this is the default):
SimpleRetryPolicy policy = new SimpleRetryPolicy(5, Collections.singletonMap(Exception.class, true));

// Use the policy...
RetryTemplate template = new RetryTemplate();
template.setRetryPolicy(policy);
template.execute(new RetryCallback<MyObject, Exception>() {
    public MyObject doWithRetry(RetryContext context) {
        // business logic here
    }
});

监听器 

Spring Retry提供了RetryListener接口。RetryTemplate允许您注册RetryListener实例。

template.registerListener(new RetryListener() {
    @Override
    public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
        return false;
    }
    @Override
    public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {

    }
    @Override
    public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {

    }
});

反射方法调用的监听器

template.registerListener(new MethodInvocationRetryListenerSupport() {
    @Override
    protected <T, E extends Throwable> void doClose(RetryContext context, MethodInvocationRetryCallback<T, E> callback, Throwable throwable) {
        super.doClose(context, callback, throwable);
    }

    @Override
    protected <T, E extends Throwable> void doOnError(RetryContext context, MethodInvocationRetryCallback<T, E> callback, Throwable throwable) {
        super.doOnError(context, callback, throwable);
    }

    @Override
    protected <T, E extends Throwable> boolean doOpen(RetryContext context, MethodInvocationRetryCallback<T, E> callback) {
        return super.doOpen(context, callback);
    }
});

Spring Retry

Spring Retry

2. 声明式

@EnableRetry
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

@Service
class Service {
    @Retryable(RemoteAccessException.class)
    public void service() {
        // ... do something
    }
    @Recover
    public void recover(RemoteAccessException e) {
       // ... panic
    }
}

可以将@EnableRetry注释添加到@Configuration类上,并在想要重试的方法上(或在所有方法的类型级别上)使用@Retryable,还可以指定任意数量的重试监听器。

@Configuration
@EnableRetry
public class Application {

    @Bean 
    public RetryListener retryListener1() {
        return new RetryListener() {...}
    }

    @Bean 
    public RetryListener retryListener2() {
        return new RetryListener() {...}
    }

}

@Service
class MyService {
    @Retryable(RemoteAccessException.class)
    public void hello() {
        // ... do something
    }
}

可以利用 @Retryable 的属性来控制 RetryPolicy 和 BackoffPolicy

@Service
public class MyService {
    @Retryable(value = RuntimeException.class, maxAttempts = 5, backoff = @Backoff(value = 1000L, multiplier = 1.5))
    public void sayHello() {
        //  ... do something
    }

    @Retryable(value = {IOException.class, RemoteAccessException.class},
            listeners = {"myListener1", "myListener2", "myListener3"},
            maxAttempts = 5, backoff = @Backoff(delay = 100, maxDelay = 500))
    public void sayHi() {
        //  ... do something
    }

    @Retryable(maxAttempts = 5, backoff = @Backoff(delay = 1000, maxDelay = 30000, multiplier = 1.2, random = true))
    public void sayBye() {
        //  ... do something
    }
}

如果希望在重试耗尽时执行另外的逻辑,则可以提供恢复方法。恢复方法应该在与@Retryable实例相同的类中声明,并标记为@Recover。返回类型必须匹配@Retryable方法。恢复方法的参数可以选择性地包括抛出的异常和(可选地)传递给原始可重试方法的参数(或它们的部分列表,只要在最后一个需要的参数之前没有被省略)。

@Service
class MyService {
    @Retryable(RemoteAccessException.class)
    public void service(String str1, String str2) {
        // ... do something
    }
    @Recover
    public void recover(RemoteAccessException e, String str1, String str2) {
       // ... error handling making use of original args if required
    }
}

为了避免多个恢复方法搞混淆了,可以手动指定用哪个恢复方法

@Service
class Service {
    @Retryable(recover = "service1Recover", value = RemoteAccessException.class)
    public void service1(String str1, String str2) {
        // ... do something
    }

    @Retryable(recover = "service2Recover", value = RemoteAccessException.class)
    public void service2(String str1, String str2) {
        // ... do something
    }

    @Recover
    public void service1Recover(RemoteAccessException e, String str1, String str2) {
        // ... error handling making use of original args if required
    }

    @Recover
    public void service2Recover(RemoteAccessException e, String str1, String str2) {
        // ... error handling making use of original args if required
    }
}

1.3.2及以后版本支持匹配参数化(泛型)返回类型来检测正确的恢复方法:

@Service
class Service {

    @Retryable(RemoteAccessException.class)
    public List<Thing1> service1(String str1, String str2) {
        // ... do something
    }

    @Retryable(RemoteAccessException.class)
    public List<Thing2> service2(String str1, String str2) {
        // ... do something
    }

    @Recover
    public List<Thing1> recover1(RemoteAccessException e, String str1, String str2) {
       // ... error handling for service1
    }

    @Recover
    public List<Thing2> recover2(RemoteAccessException e, String str1, String str2) {
       // ... error handling for service2
    }
}

1.2版本引入了对某些属性使用表达式的能力

@Retryable(exceptionExpression="message.contains('this can be retried')")
public void service1() {
  ...
}

@Retryable(exceptionExpression="message.contains('this can be retried')")
public void service2() {
  ...
}

@Retryable(exceptionExpression="@exceptionChecker.shouldRetry(#root)",
    maxAttemptsExpression = "#{@integerFiveBean}",
    backoff = @Backoff(delayExpression = "#{1}", maxDelayExpression = "#{5}", multiplierExpression = "#{1.1}"))
public void service3() {
  ...
}

表达式可以包含属性占位符,比如:#{${max.delay}} 或者 #{@exceptionChecker.${retry.method}(#root)} 。规则如下:

  • exceptionExpression 以抛出的异常为根对象进行计算求值的
  • maxAttemptsExpression 和 @BackOff 表达式属性 只在初始化的时候被计算一次。它们没有用于计算的根对象,但它们可以引用上下文中的其他bean

例如:

@Data
@Component("runtimeConfigs")
@ConfigurationProperties(prefix = "retry.cfg")
public class MyRuntimeConfig {

    private int maxAttempts;

    private long initial;

    private long max;

    private double mult;
}

application.properties

retry.cfg.maxAttempts=10
retry.cfg.initial=100
retry.cfg.max=2000
retry.cfg.mult=2.0

使用变量

@Retryable(maxAttemptsExpression = "@runtimeConfigs.maxAttempts", 
        backoff = @Backoff(delayExpression = "@runtimeConfigs.initial", 
                maxDelayExpression = "@runtimeConfigs.max", multiplierExpression = "@runtimeConfigs.mult"))
public void service() {
    System.out.println(LocalDateTime.now());
    boolean flag = sendMsg();
    if (flag) {
        throw new CustomException("调用失败");
    }
}

@Retryable(maxAttemptsExpression = "args[0] == 'something' ? 3 : 1")
public void conditional(String string) {
    ...
}

最后,简单看一下源码org.springframework.retry.support.RetryTemplate#doExecute()

 Spring Retry

RetryContext是线程局部变量

Spring Retry

间隔时间是通过线程休眠来实现的

Spring Retry

Spring Retry

Spring Retry

https://github.com/spring-projects/spring-retry

 文章来源地址https://www.toymoban.com/news/detail-747852.html

到了这里,关于Spring Retry的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • rabbitmq:retry重试机制和延迟消息的实现

    rabbitmq:retry重试机制和延迟消息的实现 在消费者消费消息的时候可能会因为网络等外部原因导致消息处理失败,这个时候如果将消息直接丢弃会导致正常的业务丢失,但是如果是一条本身就有问题的消息,那么这个时候又必须丢弃掉,如果选择用channel.basicNack 或 channel.basi

    2024年02月13日
    浏览(28)
  • axios-retry插件-axios请求失败自动重试

    axios-retry 对外导出 axiosRetry() 方法: 通过对 axios 单例添加“拦截器”,来扩展实现自动重试网络请求功能。 备注:  除非  shouldResetTimeout 被设置, 这个插件 将请求超时解释为全局值, 不是针对每一个请求,二是全局的设置 Name Type Default Description retries Number 3 The number of times t

    2024年02月10日
    浏览(66)
  • 分布式重试服务平台 Easy-Retry

      在介绍这款开源产品前先给大家介绍一个开源组织:aizuda–爱组搭   可以看到Easy-Retry就是爱组搭的开源项目之一。   在分布式系统大行其道的当前,系统数据的准确性和正确性是重大的挑战,基于CAP理论,采用柔性事务,保障系统可用性以及数据的最终一致性成为

    2024年02月09日
    浏览(32)
  • 云计算运维需要经常上夜班吗?需要倒班吗?

    【导读】不少IT传统运维人员打算转行做云计算运维,但他们想知道的是,云计算运维需要经常上夜班吗?需要倒班吗?这里我们小编就来悄悄告诉大家。 云计算运维需要经常上夜班吗? 【回答】:其实是可以不用经常上夜班的。上云计算了,不是之前那种一天到晚守着机房

    2024年02月16日
    浏览(27)
  • eclipse中经常遇到的maven相关的问题

    maven工程依赖的jar包无法部署到tomcat中 右键maven工程,选择“属性”   将工程在tomcat重新发布即可。 2、Update Project or use Quick Fix maven工程总是提示更新,一更新java版本又回到1.5 在pom.xml添加如下:

    2024年02月16日
    浏览(31)
  • selenium遇到高德地图反爬(网络拥堵,请稍后重试)

    这里为了避免被封IP,我使用了IP代理。 2.通过selenium自带的函数来获取页面的cookie信息,每点击一次会生成新的cookie,我再把新的cookie加入到当前页面中。 3.通过这样就可以实现通过搜索位置名来获取地理位置信息,或者是商铺信息了。 需要源码私聊我~~~  Q群交流:4502973

    2024年02月08日
    浏览(31)
  • 工作中,我们经常用到哪些SQL语句呢?

    目录 一、DDL部分(create、drop、alter) 1.1 create 语句上 1.2 drop 语句 1.3 alter 语句 二、DML(数据操纵语言)和DQL(数据查询语言) 2.1 insert 语句 2.2 update 语句 2.3 delete 语句 2.4 select 语句 2.5 其他操纵语言 2.5.1 truncate 语句 2.5.2 merge 语句 三、用户角色权限 3.1 用户相关 3.1.1 创建用户

    2024年02月03日
    浏览(34)
  • Spring Retry

    工作中,经常遇到需要重试的场景,最简单的方式可以用try...catch...加while循环来实现。那么,有没有统一的、优雅一点儿的处理方式呢?有的,Spring Retry就可以帮我们搞定重试问题。 关于重试,我们可以关注以下以下几个方面: 什么情况下去触发重试机制 重试多少次,重试

    2024年02月05日
    浏览(34)
  • spring retry 配置及使用

    依赖 开始使用 代码示例 业务注解 代码示例 注意 两个方法的返回值要一样,否则是不起作用的 NullPointerException 必须要将异常类型当作参数传入 如果不这样的话无法进行回调,当然不配置 @Recever 也可以,那就不会有回调处理了 写一个action 调用一下方法 输出结果 以上最简单

    2024年01月19日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包