消息重试框架 Spring-Retry 和 Guava-Retry

  • 一 重试框架之Spring-Retry

    • 1.Spring-Retry的普通使用方式

    • 2.Spring-Retry的注解使用方式

  • 二 重试框架之Guava-Retry

  • 总结

图片


一 重试框架之Spring-Retry

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

1. Spring-Retry的普通使用方式

1.准备工作

我们只需要加上依赖:

 <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,抛出远程访问异常");}}}
2.使用SpringRetryTemplate

这里可以写我们的代码了

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

图片

图片

2. 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只能根据抛出的异常来进行重试。

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

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

相关文章

Maven -- <dependencyManagement>管理子项目版本

背景&#xff1a; 一个旧项目&#xff0c;想使用mybatis-plus&#xff0c;想着这是比较基础的依赖包&#xff0c;就在父项目中添加对应依赖&#xff0c;如下: <!-- 依赖声明 --><dependencyManagement><dependencies><!-- mybatis-plus 依赖配置 -->&l…

Java Arrays类

Arrays类 介绍 用于管理或操作数组(比如排序和搜索) 常用方法 1、Arrays.toString(ints)&#xff1a;返回数组的字符串形式 int[] ints {1, 2, 3, 4, 5}; System.out.println(Arrays.toString(ints));2、sort排序(自然排序和定制排序) import java.util.Arrays; import …

python实现接口压力测试

python实现接口压力测试 直接上代码&#xff1a; # -*- coding: utf-8 -*-import json import requests import logginglogging.basicConfig(levellogging.INFO, format%(asctime)s - %(name)s - %(levelname)s - %(message)s) logger logging.getLogger(__name__)restime …

LayUI之增删改查

目录 一、前言 1.1 前言 1.2 前端代码(数据表格组件) 1.3 封装JS 二、LayUI增删改查的后台代码 2.1 编写Dao方法 2.1 增加 2.2 删除 2.3 修改 三、LayUI增删改查的前端代码 3.1 增加 一、前言 1.1 前言 上一篇文章我们一起做了LayUI的动态添加选项卡&#xff0c;这一篇…

IP库新增多种颜色转换空间IP

颜色空间转换是图像及视频中常用的解决方案&#xff0c;涉及hsv-rgb、rgb-ycrcb等一些常见的颜色空间互相转换&#xff0c;今天带来几种常见的颜色空间转换IP&#xff0c;主要如下&#xff1a; IP库简介 一直想做一个可以供大家学习、使用的开源IP库&#xff0c;类似OpenCores&…

基于单片机快递柜的设计与实现

功能介绍 以51单片机作为主控系统&#xff1b;液晶显示当前信息&#xff0c;最多可存储几十个&#xff1b;按下存储按键液晶显示当前快递柜剩余数量&#xff1b;继电器打开&#xff0c;表示用来放物品&#xff1b;正次按下存储按键将取消存快递&#xff0c;继电器关闭快递柜可用…

“探索图像处理的奥秘:使用Python和OpenCV进行图像和视频处理“

1、上传图片移除背景后下载。在线抠图软件_图片去除背景 | remove.bg – remove.bg 2、对下载的图片放大2倍。ClipDrop - Image upscaler 3、对放大后的下载照片进行编辑。 4、使用deepfacelive进行换脸。 1&#xff09;将第三步的照片复制到指定文件夹。C:\myApp\deepfakeliv…

Proxmox VE 为 Windows 虚拟机添加硬盘遇到的问题

环境&#xff1a;PVE 8.x、Windows 11/Windows Server 2019 &#x1f449;问题一&#xff1a; 为 windows 虚拟机添加磁盘&#xff0c;重启虚拟机后&#xff08;在 windows 系统中重启&#xff09;磁盘未能生效&#xff0c;并显示为橘色。 ❗橘色 意味需要重启VM才能生效&…

如何应对客户报价要求过低的情况?这些方案帮你化解危机!

有个客户在寄样品之前让报价&#xff0c;并且要求承诺价格必须低于15美金&#xff0c;业务员同意了&#xff0c;让客户把样板安排寄到中国&#xff0c;但是收到样品后发现客户的样品在侧面还有一块突出的部分&#xff0c;所以15美金太低了&#xff0c;无论如何也得18美金&#…

【数据挖掘】如何为可视化准备数据

一、说明 想要开始您的下一个数据可视化项目吗&#xff1f;首先与数据清理友好。数据清理是任何数据管道中的重要步骤&#xff0c;可将原始的“脏”数据输入转换为更可靠、相关和简洁的数据输入。诸如Tableau Prep或Alteryx之类的数据准备工具就是为此目的而创建的&#xff0c;…

网络虚拟化相关的Linux接口介绍

Linux拥有丰富的网络虚拟化功能&#xff0c;能被虚拟机&#xff0c;容器还有云网络使用。在这篇文章中&#xff0c;我会给出所有通用网络虚拟化接口的简要介绍。没有代码分析&#xff0c;只有简短的接口介绍和在Linux上的使用操作。这系列接口都可以使用ip link命令实现。 这篇…

微信原生实现一个简易的图片上传功能

一、实现原理 wx.showActionSheet()&#xff1a;显示操作菜单&#xff0c;选择是从相册选择还是相机拍摄照片wx.chooseImage()&#xff1a;从本地相册选择图片或使用相机拍照。wx.uploadFile()&#xff1a;将本地资源上传到服务器。客户端发起一个 HTTPS POST 请求&#xff0c…

项目实战Qt网盘系统

背景&#xff1a;随着时代的发展&#xff0c;业务数据量的剧增及移动办公需求&#xff0c;人们对内存的需求越来越强&#xff0c;传统的存储产品&#xff0c;在容量及携带型日益不能满足人工的工作需求&#xff0c;网盘再此背景下应运而生。网盘是能够提供文件同步&#xff0c;…

回归预测 | MATLAB实现GRU(门控循环单元)多输入单输出(不调用工具箱函数)

回归预测 | MATLAB实现GRU(门控循环单元)多输入单输出(不调用工具箱函数) 文章目录 回归预测 | MATLAB实现GRU(门控循环单元)多输入单输出(不调用工具箱函数)预测效果基本介绍程序设计参考资料 预测效果 基本介绍 GRU神经网络是LSTM神经网络的一种变体&#xff0c;LSTM 神经网 …

Ubuntu 考虑采用新的 “统一默认安装 (unified default install)”

导读Ubuntu安装程序中的 “最小化安装” (Minimal installation) 是该发行版多年来最受欢迎的功能之一。 当用户选择 Ubuntu 的 “最小化安装” 选项时&#xff0c;可以在安装更少的预装应用程序情况下&#xff0c;获得完整、功能齐全的 Ubuntu 系统。 但这个功能可能要被砍掉…

个人博客系统(二)

该博客系统共有八个页面,即注册页面、登录页面、添加文章页面、修改文章页面、我的博客列表页面、主页、查看文章详情页面、个人中心页面。 1 注册页面 该页面如图所示: 首先,要先判断注册的用户名、密码、确认密码以及验证码是否为空,若有一个为空,点击提交,则会提醒 …

文献阅读:MathPrompter: Mathematical Reasoning using Large Language Models

文献阅读&#xff1a;MathPrompter: Mathematical Reasoning using Large Language Models 1. 内容简介2. 方法细节3. 实验内容4. 结论&思考 文献链接&#xff1a;https://arxiv.org/abs/2303.05398 1. 内容简介 这篇文章是今年3月份的时候微软提出的一篇工作&#xff0…

【云原生】k8s安全机制

前言 Kubernetes 作为一个分布式集群的管理工具&#xff0c;保证集群的安全性是其一个重要的任务。API Server 是集群内部各个组件通信的中介&#xff0c; 也是外部控制的入口。所以 Kubernetes 的安全机制基本就是围绕保护 API Server 来设计的。 比如 kubectl 如果想向 API…

工欲善其事,必先利其器之—react-native-debugger调试react native应用

调试react应用通常利用chrome的inspector的功能和两个最常用的扩展 1、React Developer Tools &#xff08;主要用于debug组件结构) 2、Redux DevTools &#xff08;主要用于debug redux store的数据&#xff09; 对于react native应用&#xff0c;我们一般就使用react-nativ…

Java项目查询统计表中各状态数量

框架&#xff1a;SpringBoot&#xff0c;Mybatis&#xff1b;数据库&#xff1a;MySQL 表中设计2个状态字段&#xff0c;每个字段有3种状态&#xff0c;统计这6个状态各自的数量 sql查询语句及结果如图 SQL&#xff1a; SELECT SUM(CASE WHEN A0 THEN 1 ELSE 0 END) AS A0…