重试框架入门:Spring-RetryGuava-Retry

前言

在日常工作中,随着业务日渐庞大,不可避免的涉及到调用远程服务,但是远程服务的健壮性和网络稳定性都是不可控因素,因此,我们需要考虑合适的重试机制去处理这些问题,最基础的方式就是手动重试,侵入业务代码去处理,再高端一点的通过切面去处理,较为优雅的实现重试,下面,介绍两个重试框架,只需要配置好重启策略及重试任务,即可使用。

重试任务

这里只是模拟传参、相应及异常,具体任务需对应业务

package com.example.test.MessageRetry;import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.remoting.RemoteAccessException;@Slf4j
public class RetryTask {/*** 重试方法* @param param* @return*/public static boolean retryTask(String param){log.info("请求参数:{}",param);int i = RandomUtils.nextInt(0,15);log.info("随机数:{}",i);if(i == 0){log.error("参数异常");throw new IllegalArgumentException("参数异常");}else if(i > 10){log.error("访问异常");throw new RemoteAccessException("访问异常");}else if(i % 2 == 0){log.info("成功");return true;}else{log.info("失败");return false;}}
}

Spring-Retry

简介

Spirng-Retry是Spring提供的一个重试框架,为spring程序提供声明式重试支持,主要针对可能抛出异常的调用操作,进行有策略的重试。可以通过代码方式和注解方式实现,主要由重试执行者和两个策略构成:

  • RetryTemplet:重试执行者,可以设置重试策略(设置重试上线、如何重试)和回退策略(立即重试还是等待一段时间后重试,默认立即重试,如果需要配置等待一段时间后重试则需要指定回退策略),通过Execute提交执行操作,只有在调用时抛出指定配置的异常,才会执行重试
  • 重试策略:
策略方式
NeverRetryPolicy只允许调用RetryCallback一次,不允许重试
AlwaysRetryPolicy允许无限重试,直到成功,此方式逻辑不当会导致死循环
SimpleRetryPolicy固定次数重试策略,默认重试最大次数为3次,RetryTemplate默认使用的策略
TimeoutRetryPolicy超时时间重试策略,默认超时时间为1秒,在指定的超时时间内允许重试
ExceptionClassifierRetryPolicy设置不同异常的重试策略,类似组合重试策略,区别在于这里只区分不同异常的重试
CircuitBreakerRetryPolicy有熔断功能的重试策略,需设置3个参数openTimeout、resetTimeout和delegate
CompositeRetryPolicy组合重试策略,有两种组合方式,乐观组合重试策略是指只要有一个策略允许即可以重试,悲观组合重试策略是指只要有一个策略不允许即可以重试,但不管哪种组合方式,组合中的每一个策略都会执行
  • 重试回退策略:
回退策略方式
NoBackOffPolicy无退避算法策略,每次重试时立即重试
FixedBackOffPolicy固定时间的退避策略,需设置参数sleeper和backOffPeriod,sleeper指定等待策略,默认是Thread.sleep,即线程休眠,backOffPeriod指定休眠时间,默认1秒
UniformRandomBackOffPolicy随机时间退避策略,需设置sleeper、minBackOffPeriod和maxBackOffPeriod,该策略在minBackOffPeriod,maxBackOffPeriod之间取一个随机休眠时间,minBackOffPeriod默认500毫秒,maxBackOffPeriod默认1500毫秒
ExponentialBackOffPolicy指数退避策略,需设置参数sleeper、initialInterval、maxInterval和multiplier,initialInterval指定初始休眠时间,默认100毫秒,maxInterval指定最大休眠时间,默认30秒,multiplier指定乘数,即下一次休眠时间为当前休眠时间*multiplier
ExponentialRandomBackOffPolicy随机指数退避策略,引入随机乘数可以实现随机乘数回退
  • 此外,还需要配置重试时间间隔、最大重试次数以及可重试异常

实现

代码方式

RetryTemplate通过execute提交执行操作,需要准备RetryCallback和RecoveryCallback两个类实例,前者对应的就是重试回调逻辑实例,包装正常的功能操作,RecoveryCallback实现的是整个执行操作结束的恢复操作实例,只有在调用的时候抛出了异常,并且异常是在exceptionMap中配置的异常,才会执行重试操作,否则就调用到excute方法的第二个执行方法RecoveryCallback中

package com.example.test.MessageRetry;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.remoting.RemoteAccessException;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.stereotype.Service;import java.util.HashMap;
import java.util.Map;@Service
@Slf4j
public class SpringRetryService {/*** 重试时间间隔ms,默认1000ms*/private long retryPeriodTime = 5000L;/*** 最大重试次数*/private int maxRetryNum = 3;/*** 哪些结果和异常要重试,key表示异常类型,value表示是否需要重试*/private Map<Class<? extends Throwable>,Boolean> retryMap = new HashMap<>();@Testpublic void test(){retryMap.put(IllegalArgumentException.class,true);retryMap.put(RemoteAccessException.class,true);//构建重试模板RetryTemplate retryTemplate = new RetryTemplate();//设置重试回退操作策略,主要设置重试时间间隔FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();backOffPolicy.setBackOffPeriod(retryPeriodTime);//设置重试策略,主要设置重试次数SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(maxRetryNum,retryMap);retryTemplate.setRetryPolicy(retryPolicy);retryTemplate.setBackOffPolicy(backOffPolicy);Boolean execute = retryTemplate.execute(//RetryCallbackretryContext -> {boolean b = RetryTask.retryTask("aaa");log.info("调用结果:{}",b);return b;},retryContext -> {//RecoveryCallbacklog.info("到达最多尝试次数");return false;});log.info("执行结果:{}",execute);}
}

在这里插入图片描述

注解方式

上面我们说到Spring-Retry是Spring提供的,那么,它就支持依赖整合

<!--spring-retry--><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>

然后,在启动类上添加开启注解

//表示是否开启重试,属性proxyTargetClass,boolean类型,是否创建基于子类(CGLIB)的代理,而不是标准的基于接口的代理,默认false
@EnableRetry
package com.example.test.MessageRetry;import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.remoting.RemoteAccessException;@Slf4j
public class RetryTask {/*** 重试方法* @param param* @return*/public static boolean retryTask(String param){log.info("请求参数:{}",param);int i = RandomUtils.nextInt(0,15);log.info("随机数:{}",i);if(i >-1){log.error("参数异常");throw new IllegalArgumentException("参数异常");
//        }else if(i > 10){
//            log.error("访问异常");
//            throw new RemoteAccessException("访问异常");
//        }else if(i % 2 == 0){
//            log.info("成功");
//            return true;}else{log.info("失败");return false;}}
}
/*** 重试调用方法* @param param* @return* @Retryable注解:**/@Retryable(value = {RemoteAccessException.class,IllegalArgumentException.class},maxAttempts = 3,backoff = @Backoff(delay = 5000L,multiplier = 2))public void call(String param){RetryTask.retryTask(param);}/*** 达到最大重试次数,或抛出了没有指定的异常* @param e* @param param* @return*/@Recoverpublic void recover(Exception e,String param){log.error("达到最大重试次数!!!!!");}
@Testpublic void retry(){springRetryService.call("aaa");}
  • @Retryable注解说明
属性说明
value指定重试的异常类型,默认为空
maxAttempts最大尝试次数,默认3次
include和value一样,默认为空,当exclude也为空时,默认所有异常
exclude指定不处理的异常
Backoff重试策略
  • @Backoff注解说明:设定重试倍数,每次重试时间是上次的n倍
属性说明
delay重试之间的等待时间(以毫秒为单位),默认0
maxDelay重试之间的最大等待时间(以毫秒为单位),默认0
multiplier延迟的倍数,默认0.0
delayExpression重试之间的等待时间表达式,默认空
maxDelayExpression重试之间的最大等待时间表达式,默认空
multiplierExpression指定延迟的倍数表达式,默认空
random随机指定延迟时间,默认false
  • @Recover注解说明:当重试到达指定次数时,将要回调的方法

  • @Retryable和@Recover修饰的方法要在同一个类中,且被@Retryable和@Recover标记的方法不能有返回值,这样Recover方法才会生效。由于@Retryable注解是通过切面实现的,因此我们要避免@Retryable 注解的方法的调用方和被调用方处于同一个类中,因为这样会使@Retryable 注解失效。
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/22e61f2d194e4ac0aa061fa4ca7d483f.png
    我们可以看到,Spring-Retry只能针对指定异常重试,不能根据执行结果返回值重试,整体使用也比较死板,下面,看下更加灵活的Guava-Retry。

Guava-Retry

简介

Guava-Retry是谷歌的Guava库的一个小扩展,允许为任意函数调用创建可配置的重试策略,我们可以通过构建重试实例RetryBuilder,来设置重试源、配置重试次数、重试超时时间、等待时间间隔等,实现优雅的重试机制。

  • 主要属性
属性说明
attemptTimeLimiter时间限制策略,单次任务执行时间限制,超时终止
stopStrategy停止重试策略
waitStrategy等待策略
blockStrategy任务阻塞策略,即当前任务执行完,下次任务执行前做什么,仅有线程阻塞threadSleepStrategy
retryException重试异常(重试策略)
listeners自定义重试监听器,可用于记录日志等
  • 时间限制策略
策略说明
NoAttemptTimeLimit对代理方法不添加时间限制,默认
FixedAttemptTimeLimit对代理方法的尝试添加固定时间限制
  • 重试策略(重试异常)
策略说明
retryIfException抛出 runtime 异常、checked 异常时都会重试,但是抛出 error 不会重试
retryIfRuntimeException只会在抛 runtime 异常的时候才重试,checked 异常和error 都不重试
retryIfExceptionOfType允许我们只在发生特定异常的时候才重试,比如NullPointerException 和 IllegalStateException 都属于 runtime 异常,也包括自定义的error
retryIfResult可以指定你的 Callable 方法在返回值的时候进行重试
  • 停止策略
策略说明
StopAfterDelayStrategy设定最长执行时间,无论任务执行几次,一旦超时,任务终止,返回RetryException
StopAfterAttemptStrategy设定最大尝试次数,一旦超过,返回重试异常
NeverStopStrategy一直轮询直到获取期望结果
  • 等待策略
策略说明
ExceptionWaitStrategy异常时长等待,如果抛出的是指定异常,则从传入的方法中取得等待时间并返回;如果异常不匹配,则返回等待时间为0L
CompositeWaitStrategy复合时长等待,在获取等待时间时会获取多种等待策略各自的等待时间,然后累加这些等待时间
FibonacciWaitStrategy斐波那契等待策略
ExponentialWaitStrategy指数等待时长,指数增长,若设置了最大时间,则停止,否则到Long.MAX_VALUE
IncrementingWaitStrategy递增等待,提供一个初始时长和步长,随次数叠加
RandomWaitStrategy随机等待时长,可以提供一个最大和最小时间,从范围内随机
FixedWaitStrategy固定等待时长

代码

<!--guava-retryer--><dependency><groupId>com.github.rholder</groupId><artifactId>guava-retrying</artifactId><version>2.0.0</version></dependency>
package com.example.test.MessageRetry;import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.remoting.RemoteAccessException;@Slf4j
public class RetryTask {/*** 重试方法* @param param* @return*/public static boolean retryTask(String param){log.info("请求参数:{}",param);int i = RandomUtils.nextInt(0,15);log.info("随机数:{}",i);if(i < 3){log.error("参数异常");throw new IllegalArgumentException("参数异常");}else if(i > 10){log.error("访问异常");throw new RemoteAccessException("访问异常");}else if(i % 2 == 0){log.info("成功");return true;}else{log.info("失败");return false;}}
}
package com.example.test.MessageRetry;import com.github.rholder.retry.*;
import com.google.common.base.Predicates;
import lombok.extern.slf4j.Slf4j;
import org.springframework.remoting.RemoteAccessException;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;@Service
@Slf4j
public class GuavaRetryService {public void guavaRetry(){//构建重试实例RetryBuilder,可以设置重试源,可以配置重试次数、重试超时时间、等待时间间隔Retryer<Boolean>retryer = RetryerBuilder.<Boolean>newBuilder()//设置异常重试源.retryIfExceptionOfType(RemoteAccessException.class).retryIfExceptionOfType(IllegalArgumentException.class)//设置根据结果重试  res->res==false  Predicates.containsPattern("_error$").retryIfResult(Predicates.equalTo(false))//设置等待时间.withWaitStrategy(WaitStrategies.fixedWait(3, TimeUnit.SECONDS))//设置最大重试次数.withStopStrategy(StopStrategies.stopAfterAttempt(3))//设置重试监听,可用作重试时的额外动作.withRetryListener(new RetryListener() {@Overridepublic <V> void onRetry(Attempt<V> attempt) {log.error("第【{}】次调用失败",attempt.getAttemptNumber());}})//设置阻塞策略.withBlockStrategy(BlockStrategies.threadSleepStrategy())//设置时间限制.withAttemptTimeLimiter(AttemptTimeLimiters.noTimeLimit()).build();try{retryer.call(()->RetryTask.retryTask("aaa"));}catch (Exception e){e.printStackTrace();}}
}

在这里插入图片描述

可以看到,我们设置了重试三次,超过这个限制没有执行成功,抛出了重试异常,而且也可以根据我们的返回结果来判断。

总结

Spring-Retry和Guava-Retry都是线程安全的重试框架,能够保证并发业务下重试逻辑的正确性。两者都很好的将正常方法和重试方法进行了解耦,可以设置超时时间、重试次数、间隔时间、监听结果等,相比来说,Guava-Retry比Spring-Retry更加灵活,并且可以通过返回值来进行重试,两者都是非常好的重试框架,具体的选用看相关的业务场景即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/83485.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言函数详解(1)

目录 函数是什么 C语言中函数的分类 库函数 自定义函数 函数的参数 实际参数&#xff08;实参&#xff09; 形式参数&#xff08;形参&#xff09; 函数的调用 传值调用 传址调用 练习 函数的嵌套调用和链式访问 嵌套调用 链式访问 函数是什么 数学中我们常见到函…

node笔记——调用免费qq的smtp发送html格式邮箱

文章目录 ⭐前言⭐smtp授权码获取⭐nodemailer⭐postman验证接口⭐结束 ⭐前言 大家好&#xff0c;我是yma16&#xff0c;本文分享关于node调用免费qq的smtp发送邮箱。 node系列往期文章 node_windows环境变量配置 node_npm发布包 linux_配置node node_nvm安装配置 node笔记_h…

从零实现深度学习框架——Transformer从菜鸟到高手(一)

引言 &#x1f4a1;本文为&#x1f517;[从零实现深度学习框架]系列文章内部限免文章&#xff0c;更多限免文章见 &#x1f517;专栏目录。 本着“凡我不能创造的&#xff0c;我就不能理解”的思想&#xff0c;系列文章会基于纯Python和NumPy从零创建自己的类PyTorch深度学习框…

js 正则表达式

js 正则表达式 http://tool.oschina.net/regex https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions 11 22 333

磁盘的管理

一、磁盘的分区 查看磁盘 lsblk fdisk -l 2、分区 没有e扩展&#xff0c;则都是主分区&#xff0c;已经有三个主分区了&#xff0c;剩下的全设置为扩展 查看分区结果&#xff1a; 二、格式化 三、挂载

JVM、JRE、JDK三者之间的关系

JVM、JRE和JDK是与Java开发和运行相关的三个重要概念。 再了解三者之前让我们先来了解下java源文件的执行顺序&#xff1a; 使用编辑器或IDE(集成开发环境)编写Java源文件.即demo.java程序必须编译为字节码文件&#xff0c;javac(Java编译器)编译源文件为demo.class文件.类文…

Web-WebApp Vue.js 目录结构

WebApp Vue.js 目录结构 目录解析 目录/文件 说明 build 最终发布的代码存放位置。config 配置目录&#xff0c;包括端口号等。我们初学可以使用默认的。node_modules npm 加载的项目依赖模块 src 这里是我们要开发的目录&#xff0c;基本上要做的事情都在这个目录里。里面包…

Pycharm如何打断点进行调试?

断点调试&#xff0c;是编写程序中一个很重要的步骤&#xff0c;有些简单的程序使用print语句就可看出问题&#xff0c;而比较复杂的程序&#xff0c;函数和变量较多的情况下&#xff0c;这时候就需要打断点了&#xff0c;更容易定位问题。 一、添加断点 在代码的行标前面&…

ATF(TF-A)安全通告 TFV-6 (CVE-2017-5753, CVE-2017-5715, CVE-2017-5754)

ATF(TF-A)安全通告汇总 目录 一、ATF(TF-A)安全通告 TFV-6 (CVE-2017-5753, CVE-2017-5715, CVE-2017-5754) 二、Variant 1 (CVE-2017-5753) 三、Variant 2 (CVE-2017-5715) 四、Variant 3 (CVE-2017-5754) 一、ATF(TF-A)安全通告 TFV-6 (CVE-2017-5753, CVE-2017-5715, C…

15-矩阵转置的拓展延伸

&#x1f52e;矩阵的转置✨ 前言 在很多时候我们拿到的数据本身可能并不会把点的坐标按列的方向排列起来&#xff0c;对于我们人类来说&#xff0c;更方便的方式依然是把这个点的坐标按行的方向排列&#xff0c;我们比较熟悉把矩阵看作为一个数据&#xff0c;在这里&#xff0…

06-3_Qt 5.9 C++开发指南_多窗体应用程序的设计(主要的窗体类及其用途;窗体类重要特性设置;多窗口应用程序设计)

文章目录 1. 主要的窗体类及其用途2. 窗体类重要特性的设置2.1 setAttribute()函数2.2 setWindowFlags()函数2.3 setWindowState()函数2.4 setWindowModality()函数2.5 setWindowOpacity()函数 3. 多窗口应用程序设计3.1 主窗口设计3.2 QFormDoc类的设计3.3 QFormDoc类的使用3.…

某科技公司提前批测试岗

文章目录 题目 今天给大家带来一家提前批测试岗的真题&#xff0c;目前已经发offer 题目 1.自我介绍 2.登录页面测试用例设计 3.如何模拟多用户登录 可以使用Jmeter,loadRunner性能测试工具来模拟大量用户登录操作去观察一些参数变化 4.有使用过Jmeter,loadRunner做过性能压…

数据库运维是什么意思?主要工作包含哪些?

还有不少小伙伴不知道数据库运维是什么意思&#xff1f;主要工作内容包含哪些&#xff1f;今天我们就一起来简单了解一下吧&#xff0c;仅供参考哦&#xff01; 数据库运维是什么意思&#xff1f; 数据库运维是指对数据库系统进行管理、监控和维护的过程&#xff0c;以确保数据…

ABPVNEXT-微服务框架基础入门

准备工作&#xff1a; 1.登录ABPvNext官网 网址 http://abp.io 2.跳转到商业版的说明文档&#xff0c;目前商业版没有中文&#xff0c;只能使用谷歌浏览器的内置翻译功能了 3.框架的相关环境要求&#xff0c;请自自行查看 适用于 Windows 的Visual Studio 2022 (v17.3) /…

适配器模式

泰国旅游使用插座问题 泰国插座用的是两孔的&#xff08;欧标&#xff09;&#xff0c;可以买个 多功能转换插头&#xff08;适配器&#xff09;&#xff0c;这样就可以使用了 适配器模式基本介绍 适配器模式&#xff08;Adapter Pattern&#xff09;将某个类的接口转换成客户端…

【M波段2D双树(希尔伯特)小波多分量图像去噪】基于定向M波段双树(希尔伯特)小波对多分量/彩色图像进行降噪研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

修改el-select样式;修改element-plus的下拉框el-select样式

修改el-select样式 .select_box{// 默认placeholder:deep .el-input__inner::placeholder {font-size: 14px;font-weight: 500;color: #3E534F;}// 默认框状态样式更改:deep .el-input__wrapper {height: 42px;background-color: rgba(0,0,0,0)!important;box-shadow: 0 0 0 …

PHP实现在线进制转换器,10进制,2、4、8、16、32进制转换

1.接口文档 2.laravel实现代码 /*** 进制转换计算器* return \Illuminate\Http\JsonResponse*/public function binaryConvertCal(){$ten $this->request(ten);$two $this->request(two);$four $this->request(four);$eight $this->request(eight);$sixteen …

状态模式(C++)

定义 允许一个对象在其内部状态改变时改变它的行为。从而使对象看起来似乎修改了其行为。 应用场景 在软件构建过程中&#xff0c;某些对象的状态如果改变&#xff0c;其行为也会随之&#xff0c;而发生变化&#xff0c;比如文档处于只读状态&#xff0c;其支持的行为和读写…