Spring Retry 为 Spring 应用程序提供了声明性重试支持。它用于Spring批处理、Spring集成、Apache Hadoop(等等)。它主要是针对可能抛出异常的一些调用操作,进行有策略的重试

一、 Spring-Retry的普通使用方式

我们只需要加上依赖,代码如下:

 <dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</artifactId><version>1.2.2.RELEASE</version></dependency>

准备一个任务方法,我这里是采用一个随机整数,根据不同的条件返回不同的值,或者抛出异常

package com.zgd.demo.thread.retry;import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.remoting.RemoteAccessException;/*** @Author: zgd* @Description:*/
@Slf4j
public class RetryDemoTask {/*** 重试方法* @return*/public static boolean retryTask(String param)  {log.info("收到请求参数:{}",param);int i = RandomUtils.nextInt(0,11);log.info("随机生成的数:{}",i);if (i == 0) {log.info("为0,抛出参数异常.");throw new IllegalArgumentException("参数异常");}else if (i  == 1){log.info("为1,返回true.");return true;}else if (i == 2){log.info("为2,返回false.");return false;}else{//为其他log.info("大于2,抛出自定义异常.");throw new RemoteAccessException("大于2,抛出远程访问异常");}}}

这里可以写我们的代码了

package com.zgd.demo.thread.retry.spring;import com.zgd.demo.thread.retry.RetryDemoTask;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.remoting.RemoteAccessException;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;import java.util.HashMap;
import java.util.Map;/*** @Author: zgd* @Description: spring-retry 重试框架*/
@Slf4j
public class SpringRetryTemplateTest {/*** 重试间隔时间ms,默认1000ms* */private long fixedPeriodTime = 1000L;/*** 最大重试次数,默认为3*/private int maxRetryTimes = 3;/*** 表示哪些异常需要重试,key表示异常的字节码,value为true表示需要重试*/private Map<Class<? extends Throwable>, Boolean> exceptionMap = new HashMap<>();@Testpublic void test() {exceptionMap.put(RemoteAccessException.class,true);// 构建重试模板实例RetryTemplate retryTemplate = new RetryTemplate();// 设置重试回退操作策略,主要设置重试间隔时间FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();backOffPolicy.setBackOffPeriod(fixedPeriodTime);// 设置重试策略,主要设置重试次数SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(maxRetryTimes, exceptionMap);retryTemplate.setRetryPolicy(retryPolicy);retryTemplate.setBackOffPolicy(backOffPolicy);Boolean execute = retryTemplate.execute(//RetryCallbackretryContext -> {boolean b = RetryDemoTask.retryTask("abc");log.info("调用的结果:{}", b);return b;},retryContext -> {//RecoveryCallbacklog.info("已达到最大重试次数或抛出了不重试的异常~~~");return false;});log.info("执行结果:{}",execute);}}

简单剖析下案例代码:

  • RetryTemplate 承担了重试执行者的角色,它可以设置 SimpleRetryPolicy(重试策略,设置重试上限,重试的根源实体),FixedBackOffPolicy(固定的回退策略,设置执行重试回退的时间间隔)。
  • RetryTemplate通过execute提交执行操作,需要准备RetryCallback 和RecoveryCallback 两个类实例,前者对应的就是重试回调逻辑实例,包装正常的功能操作,RecoveryCallback实现的是整个执行操作结束的恢复操作实例.

只有在调用的时候抛出了异常,并且异常是在exceptionMap中配置的异常,才会执行重试操作,否则就调用到excute方法的第二个执行方法RecoveryCallback

当然,重试策略还有很多种,回退策略也是:

        重试策略

  • NeverRetryPolicy: 只允许调用RetryCallback一次,不允许重试
  • AlwaysRetryPolicy: 允许无限重试,直到成功,此方式逻辑不当会导致死循环

  • SimpleRetryPolicy: 固定次数重试策略,默认重试最大次数为3次,RetryTemplate默认使用的策略

  • TimeoutRetryPolicy: 超时时间重试策略,默认超时时间为1秒,在指定的超时时间内允许重试

  • ExceptionClassifierRetryPolicy: 设置不同异常的重试策略,类似组合重试策略,区别在于这里只区分不同异常的重试

  • CircuitBreakerRetryPolicy: 有熔断功能的重试策略,需设置3个参数openTimeoutresetTimeoutdelegate

  • CompositeRetryPolicy: 组合重试策略,有两种组合方式,乐观组合重试策略是指只要有一个策略允许即可以重试,悲观组合重试策略是指只要有一个策略不允许即可以重试,但不管哪种组合方式,组合中的每一个策略都会执行

重试回退策略

重试回退策略,指的是每次重试是立即重试还是等待一段时间后重试。

默认情况下是立即重试,如果需要配置等待一段时间后重试则需要指定回退策略BackoffRetryPolicy

  • NoBackOffPolicy: 无退避算法策略,每次重试时立即重试

  • FixedBackOffPolicy: 固定时间的退避策略,需设置参数sleeperbackOffPeriodsleeper指定等待策略,默认是Thread.sleep,即线程休眠,backOffPeriod指定休眠时间,默认1秒

  • UniformRandomBackOffPolicy: 随机时间退避策略,需设置sleeperminBackOffPeriodmaxBackOffPeriod,该策略在minBackOffPeriod,maxBackOffPeriod之间取一个随机休眠时间,minBackOffPeriod默认500毫秒,maxBackOffPeriod默认1500毫秒

  • ExponentialBackOffPolicy: 指数退避策略,需设置参数sleeperinitialIntervalmaxIntervalmultiplier,initialInterval指定初始休眠时间,默认100毫秒,maxInterval指定最大休眠时间,默认30秒,multiplier指定乘数,即下一次休眠时间为当前休眠时间*multiplier

  • ExponentialRandomBackOffPolicy: 随机指数退避策略,引入随机乘数可以实现随机乘数回退

我们可以根据自己的应用场景和需求,使用不同的策略,不过一般使用默认的就足够了。

上面的代码的话,我简单的设置了重试间隔为1秒,重试的异常是RemoteAccessException,下面就是测试代码的情况: 重试第二次成功的情况:

重试一次以后,遇到了没有指出需要重试的异常,直接结束重试,调用retryContext

重试了三次后,达到了最大重试次数,调用retryContext

二、Spring-Retry的注解使用方式

既然是Spring家族的东西,那么自然就支持和Spring-Boot整合

1.准备工作

 <dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</artifactId><version>1.2.2.RELEASE</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.1</version></dependency>

2.代码

在application启动类上加上@EnableRetry的注解

@EnableRetry
public class Application {...
}

为了方便测试,我这里写了一个SpringBootTest的测试基类,需要使用SpringBootTest的只要继承这个类就好了

package com.zgd.demo.thread.test;/*** @Author: zgd* @Description:*/import com.zgd.demo.thread.Application;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;/*** @Author: zgd* @Date: 18/09/29 20:33* @Description:*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@Slf4j
public class MyBaseTest {@Beforepublic void init() {log.info("----------------测试开始---------------");}@Afterpublic void after() {log.info("----------------测试结束---------------");}}

我们只要在需要重试的方法上加@Retryable,在重试失败的回调方法上加@Recover,下面是这些注解的属性

建一个service类

package com.zgd.demo.thread.retry.spring;import com.zgd.demo.thread.retry.RetryDemoTask;
import com.zgd.demo.thread.test.MyBaseTest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.remoting.RemoteAccessException;
import org.springframework.retry.ExhaustedRetryException;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Component;/*** @Author: zgd* @Description:*/
@Service
@Slf4j
public class SpringRetryDemo   {/*** 重试所调用方法* @param param* @return*/@Retryable(value = {RemoteAccessException.class},maxAttempts = 3,backoff = @Backoff(delay = 2000L,multiplier = 2))public boolean call(String param){return RetryDemoTask.retryTask(param);}/*** 达到最大重试次数,或抛出了一个没有指定进行重试的异常* recover 机制* @param e 异常*/@Recoverpublic boolean recover(Exception e,String param) {log.error("达到最大重试次数,或抛出了一个没有指定进行重试的异常:",e);return false;}}

然后我们调用这个service里面的call方法

package com.zgd.demo.thread.retry.spring;import com.zgd.demo.thread.test.MyBaseTest;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;/*** @Author: zgd* @Description:*/
@Component
@Slf4j
public class SpringRetryDemoTest extends MyBaseTest {@Autowiredprivate SpringRetryDemo springRetryDemo;@Testpublic void retry(){boolean abc = springRetryDemo.call("abc");log.info("--结果是:{}--",abc);}}

这里我依然是RemoteAccessException的异常才重试,@Backoff(delay = 2000L,multiplier = 2))表示第一次间隔2秒,以后都是次数的2倍,也就是第二次4秒,第三次6秒.

        来测试一下:

遇到了没有指定重试的异常,这里指定重试的异常是 @Retryable(value = {RemoteAccessException.class}...,所以抛出参数异常IllegalArgumentException的时候,直接回调@Recover的方法

重试达到最大重试次数时,调用@Recover的方法

重试到最后一次没有报错,返回false

三、重试框架之Guava-Retry

Guava retryer工具与spring-retry类似,都是通过定义重试者角色来包装正常逻辑重试,但是Guava retryer有更优的策略定义,在支持重试次数和重试频度控制基础上,能够兼容支持多个异常或者自定义实体对象的重试源定义,让重试功能有更多的灵活性。

Guava Retryer也是线程安全的,入口调用逻辑采用的是Java.util.concurrent.Callable的call方法,示例代码如下:

pom.xml加入依赖

  <!-- https://mvnrepository.com/artifact/com.github.rholder/guava-retrying --><dependency><groupId>com.github.rholder</groupId><artifactId>guava-retrying</artifactId><version>2.0.0</version></dependency>

更改一下测试的任务方法

package com.zgd.demo.thread.retry;import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.remoting.RemoteAccessException;/*** @Author: zgd* @Description:*/
@Slf4j
public class RetryDemoTask {/*** 重试方法* @return*/public static boolean retryTask(String param)  {log.info("收到请求参数:{}",param);int i = RandomUtils.nextInt(0,11);log.info("随机生成的数:{}",i);if (i < 2) {log.info("为0,抛出参数异常.");throw new IllegalArgumentException("参数异常");}else if (i  < 5){log.info("为1,返回true.");return true;}else if (i < 7){log.info("为2,返回false.");return false;}else{//为其他log.info("大于2,抛出自定义异常.");throw new RemoteAccessException("大于2,抛出自定义异常");}}}

Guava 这里设定跟Spring-Retry不一样,我们可以根据返回的结果来判断是否重试,比如返回false我们就重试

package com.zgd.demo.thread.retry.guava;import com.github.rholder.retry.*;
import com.zgd.demo.thread.retry.RetryDemoTask;
import org.junit.Test;
import org.springframework.remoting.RemoteAccessException;import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;/*** @Author: zgd* @Description:*/
public class GuavaRetryTest {@Testpublic void fun01(){// RetryerBuilder 构建重试实例 retryer,可以设置重试源且可以支持多个重试源,可以配置重试次数或重试超时时间,以及可以配置等待时间间隔Retryer<Boolean> retryer = RetryerBuilder.<Boolean> newBuilder().retryIfExceptionOfType(RemoteAccessException.class)//设置异常重试源.retryIfResult(res-> res==false)  //设置根据结果重试.withWaitStrategy(WaitStrategies.fixedWait(3, TimeUnit.SECONDS)) //设置等待间隔时间.withStopStrategy(StopStrategies.stopAfterAttempt(3)) //设置最大重试次数.build();try {retryer.call(() -> RetryDemoTask.retryTask("abc"));} catch (Exception e) {e.printStackTrace();}}}

运行测试一下遇到了我们指定的需要重试的异常,进行重试,间隔是3秒重试次数超过了最大重试次数返回为true,直接结束重试遇到了没有指定重试的异常,结束重试返回false,重试我们可以更灵活的配置重试策略,比如:

  • retryIfException: retryIfException,抛出 runtime 异常、checked 异常时都会重试,但是抛出 error 不会重试。

  • retryIfRuntimeException: retryIfRuntimeException 只会在抛 runtime 异常的时候才重试,checked 异常和error 都不重试。

  • retryIfExceptionOfType: retryIfExceptionOfType 允许我们只在发生特定异常的时候才重试,比如NullPointerException 和 IllegalStateException 都属于 runtime 异常,也包括自定义的error

如:

retryIfExceptionOfType(NullPointerException.class)// 只在抛出空指针异常重试
  • retryIfResult: retryIfResult 可以指定你的 Callable 方法在返回值的时候进行重试,如

// 返回false重试
.retryIfResult(Predicates.equalTo(false))   //以_error结尾才重试
.retryIfResult(Predicates.containsPattern("_error$"))//返回为空时重试
.retryIfResult(res-> res==null)
  • RetryListener: 当发生重试之后,假如我们需要做一些额外的处理动作,比如log一下异常,那么可以使用RetryListener。每次重试之后,guava-retrying 会自动回调我们注册的监听。可以注册多个RetryListener,会按照注册顺序依次调用。

.withRetryListener(new RetryListener {      @Override    public <T> void onRetry(Attempt<T> attempt) {  logger.error("第【{}】次调用失败" , attempt.getAttemptNumber());  } }
)

四、总结

spring-retry 和 guava-retry 工具都是线程安全的重试,能够支持并发业务场景的重试逻辑正确性。两者都很好的将正常方法和重试方法进行了解耦,可以设置超时时间、重试次数、间隔时间、监听结果、都是不错的框架。

但是明显感觉得到,guava-retry在使用上更便捷,更灵活,能根据方法返回值来判断是否重试,而Spring-retry只能根据抛出的异常来进行重试。

SpringBoot 消息重试框架 spring-retry 和 guava-retry 详解相关推荐

  1. spring boot application.properties 属性详解

    2019年3月21日17:09:59 英文原版: https://docs.spring.io/spring-boot/docs/current/reference/html/common-appli ...

  2. spring(7)---深入理解Spring核心技术——Spring中的各模块详解

    深入理解Spring核心技术--Spring中的各模块详解 Spring框架的两个基本概念IOC容器和AOP,相信大家现在对Spring中的这两个部分的基本概念有了一定的认识,好了,那么今天我们就来正 ...

  3. 【JAVA秘籍心法篇-Spring】Spring XML解析源码详解

    [JAVA秘籍心法篇-Spring]Spring XML解析源码详解 所谓天下武功,无坚不摧,唯快不破.但有又太极拳法以快制慢,以柔克刚.武功外式有拳打脚踢,刀剑棍棒,又有内功易筋经九阳神功.所有外功 ...

  4. Spring 3.0 注解注入详解

    Spring 3.0 注解注入详解 2011-04-15 09:44 17ZOUGUO ITEYE博客 我要评论(1) 字号:T | T AD: 一.各种注解方式 1.@Autowired注解(不推荐 ...

  5. 三大框架题目整合考试题(含详解)

    三大框架题目整合考试题(含详解) 1.在Hibernate的关联关系映射配置中,下列选项对于inverse说法错误的是(bd). (选择二项) A. inverse属性指定了关联关系中的方向 //in ...

  6. Spring Boot的启动器Starter详解

    Spring Boot的启动器Starter详解 作者:chszs,未经博主允许不得转载.经许可的转载需注明作者和博客主页:http://blog.csdn.net/chszs Spring Boot ...

  7. 自动化测试框架[Cypress命令行执行测试详解]

    前提 已经熟练掌握了Cypress的基本知识,请参考自动化测试框架[Cypress概述]和自动化测试框架[各自动化测试框架比较] 已经熟练掌握Cypress环境配置,请参考自动化测试框架[Cypres ...

  8. 【夯实Spring Cloud】Spring Cloud分布式配置中心详解

    本文属于[夯实Spring Cloud]系列文章,该系列旨在用通俗易懂的语言,带大家了解和学习Spring Cloud技术,希望能给读者带来一些干货.系列目录如下: [夯实Spring Cloud]D ...

  9. ElasticSearch——Spring Boot 集成 ES 操作详解

    文章目录 ElasticSearch--Spring Boot 集成 ES 操作详解 1.SpringBoot 集成 ES 2.索引的API操作详解 3.文档的API操作详解 ElasticSearc ...

最新文章

  1. McCabe度量法计算程序的环路复杂性
  2. golang多核的使用
  3. b+树时间复杂度_深入理解数据库系统之存储存引擎(二叉搜索树)
  4. 在Sql2005中,向表中插入数据时遇到uniqueidentifier列,如何插入数据?
  5. 【转】让itunes下载加速的真正办法,转向至香港台湾澳门苹果服务器 -- 不错不错!!!...
  6. SpringMVC参数的传递——接收List数组类型的数据
  7. Java 中的细节补充
  8. jpa 多字段like_Spring Data JPA 如何进行模糊查询(LIKE) ?
  9. 嵌入式编程(一):51单片机如何将函数 定义到指定程序地址
  10. win10系统如何启动sql服务器,升级win10后sql2005 sql服务无法启动如何解决
  11. 交通标志 | 中美两国警告标志异同分析
  12. 微信小程序实战之 goods(订餐页)
  13. 【简单实用】百度网盘提速方法,不用破解和插件
  14. 色环电阻、色环电容的识别方法
  15. 牛客网刷题java之(斐波那契数列)一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
  16. Bazel5.0.0源码安装报错:no matching function for call to ‘StrFormat(const char [28], const char*)’
  17. c语言 原子 字符串,Atomic operations library(原子操作库)
  18. 基于STM32的Flash擦除方式
  19. Nginx——nginx作为静态资源web服务(CDN场景)
  20. 王慧文:高手如何做决策?

热门文章

  1. linux ubuntu 18.04设置锁屏时间和用户登录超时时间
  2. 通达信 python插件选股_[转载]通达信插件选股(基于通达信插件编程规范的简单分析)...
  3. 发表教育教学类期刊《教育信息化论坛》杂志简介及投稿须知
  4. 在ArcMap中制作点阵地图
  5. css鲸鱼漂浮动画js特效
  6. JLINK、ULINK和STlink仿真器详解
  7. 手机如何制作gif动图?一招教你快速制作gif动图
  8. Windows 7远程桌面连接Ubuntu 12.04
  9. 如何快速对接一个开发中的项目
  10. 腰间盘突出原因及治疗//2021-2-7