当前位置: 首页 > news >正文

深入了解Spring重试组件spring-retry

在我们的项目中,为了提高程序的健壮性,很多时候都需要有重试机制进行兜底,最多就场景就比如调用远程的服务,调用中间件服务等,因为网络是不稳定的,所以在进行远程调用的时候偶尔会产生超时的异常,所以一般来说我们都会通过手动去写一些重试的代码去进行兜底,而这些重试的代码其实都是模板化的,因此Spring实现了自己的重试机制组件spring-retry,下面我们就一起来学习一下spring-retry这个组件吧

使用方式

1.编程式

// 创建一个RetryTemplate
RetryTemplate retryTemplate = RetryTemplate.builder().customPolicy(new SimpleRetryPolicy()) // 指定重试策略,默认重试3次.exponentialBackoff(1000L, 2, 10000L) // 指定重试的退避时间策略.withListener(new RetryListenerDemo())// 重试监听器.build();// 通过RetryTemplate的execute方法执行业务逻辑
retryTemplate.execute(retryContext -> {log.info("开始执行");throw new RuntimeException("抛出异常");
}, context -> recoverMethod());
// 当重试结束还是失败之后最后兜底执行的方法
public String recoverMethod() {log.info("执行恢复");return "执行了Recover方法";
}
public class RetryListenerDemo implements RetryListener {@Overridepublic <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {log.info("{}", context.getRetryCount());log.info("listener>>>开始监听");//        return false; // 否决整个重试return true; // 继续重试}@Overridepublic <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {log.info("listener>>>关闭");}@Overridepublic <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {log.info("listener>>>报错了");}
}

这里说一下重试监听器,自定义的重试监听器需要实现RetryListener接口,该接口主要包括三个方法:

  • open:在执行我们的业务逻辑之前会执行一次open方法,如果该方法返回false,则会直接抛出一个TerminatedRetryException异常,从而不会往下执行业务逻辑,返回true则正常往下执行
  • close:当重试结束之后,或者open方法返回false的时候就会触发close方法
  • onError:在每一次业务逻辑抛出异常的时候都会执行onError方法

2.声明式

@Retryable(value = Exception.class, maxAttempts = 3, listeners = {"retryListenerDemo"})
public String test() {log.info("开始执行");throw new RuntimeException("抛出异常");
}
@Recover
public String recoverMethod() {log.info("执行恢复");return "执行了Recover方法";
}

声明式只需要在需要重试的方法上加上Retryable注解,并且在注解上指定一些重试的属性,比如重试次数,触发重试的异常,重试监听器等等,这些属性对应在编程式中都能进行设置。而对于重试兜底方法则需要Recover注解进行标识

重试策略RetryPolicy

在对重试属性进行配置的时候我们可以去配置不同的重试策略,所谓的重试策略,其实就是去判断是否能够进行重试,也就是RetryPolicy,它是一个接口

public interface RetryPolicy extends Serializable {/*** 是否能够重试** @param context 重试上下文* @return true=>允许重试,false=>不允许重试*/boolean canRetry(RetryContext context);/*** 获取一个重试上下文,不同的重试策略有自己的重试上下文** @param parent 父级重试上下文* @return a {@link RetryContext} object specific to this policy.**/RetryContext open(RetryContext parent);/*** 关闭这个重试上下文*/void close(RetryContext context);/*** 每一次重试失败后会回调该方法,然后通过重试上下文记录下重试的异常,方便在下一次canRetry方法中从重试上下文中去判断是否还能进行重试* @param context 重试上下文* @param throwable 重试时抛出的异常*/void registerThrowable(RetryContext context, Throwable throwable);}

该接口在spring-retry中提供多种不同的重试策略的实现

  • SimpleRetryPolicy:这是一种简单的重试策略,允许根据最大重试次数和特定的异常列表来控制重试行为
  • NeverRetryPolicy:不进行重试的重试策略,也就是说我们的业务逻辑代码在第一次执行如果抛出异常了,不会进行重试
  • AlwaysRetryPolicy:允许一直重试的重试策略
  • TimeoutRetryPolicy:通过设置重试的时间段,仅允许在未超过该时间段的时候才进行重试
  • CompositeRetryPolicy:组合重试策略,可以组合多种重试策略,这对于需要复杂条件的情况非常有用
  • ExpressionRetryPolicy:该策略继承了SimpleRetryPolicy,在SimpleRetryPolicy的基础上加上了基于spel表达式去判断是否需要进行重试的功能

在RetryPolicy接口中关键的方法就是canRetry,canRetry方法会在重试之前进行调用,用来判断是否还能够继续进行重试,而判断所需的一些上下文属性(例如已经重试的次数,重试的超时时间)就在重试上下文RetryContext中,对于每一种重试策略来说,都会有自己的RetryContext,因为不同的重试策略它们用来判断重试机会的时候所需的上下文属性是不一样的

以TimeoutRetryPolicy为例,它具有限制重试时间的功能,那自然就需要记录下重试的起始时间和重试的超时时间了,而这两个信息就会放在TimeoutRetryContext中

private static class TimeoutRetryContext extends RetryContextSupport {/*** 允许重试的时间段*/private long timeout;/*** 重试开始时间*/private long start;public TimeoutRetryContext(RetryContext parent, long timeout) {super(parent);this.start = System.currentTimeMillis();this.timeout = timeout;}/*** 判断当前是否超过了重试时间* @return true=>允许重试,false=>已经超过了重试时间了,不允许重试*/public boolean isAlive() {return (System.currentTimeMillis() - start) <= timeout;}}

这样就可以在下一次判断是否能够重试的时候,也就是调用canRetry方法的时候通过传入TimeoutRetryContext去判断重试是否超时了

退避策略BackOffPolicy

上面说的RetryPolicy主要是在每一次要重试之前用来判断是否能够进行重试的,而BackOffPolicy则是提供了重试之间的间隔时间多久的功能,也就是说会先去执行RetryPolicy判断是否允许重试,如果允许重试,则才会去执行BackOffPolicy去等待重试的执行

public interface BackOffPolicy {/*** 创建一个退避上下文** @param context the {@link RetryContext} context, which might contain information* that we can use to decide how to proceed.* @return the implementation-specific {@link BackOffContext} or '<code>null</code>'.*/BackOffContext start(RetryContext context);/*** 执行退避操作* @param backOffContext the {@link BackOffContext}* @throws BackOffInterruptedException if the attempt at back off is interrupted.*/void backOff(BackOffContext backOffContext) throws BackOffInterruptedException;}

spring-retry也提供了不同的BackOffPolicy实现

  • NoBackOffPolicy:一个不执行任何操作的 BackOffPolicy,即不会增加等待时间。适用于不需要等待时间间隔的情况
  • FixedBackOffPolicy:以固定时间去进行重试退避
  • ExponentialBackOffPolicy:退避时间以指数形式增长

执行流程

protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback,RecoveryCallback<T> recoveryCallback, RetryState state) throws E, ExhaustedRetryException {RetryPolicy retryPolicy = this.retryPolicy;BackOffPolicy backOffPolicy = this.backOffPolicy;// 获取当前的重试上下文RetryContext context = open(retryPolicy, state);if (this.logger.isTraceEnabled()) {this.logger.trace("RetryContext retrieved: " + context);}// 把当前的重试上下文设置到ThreadLocal中RetrySynchronizationManager.register(context);Throwable lastException = null;boolean exhausted = false;try {// 遍历所有的重试监听器,执行其open方法boolean running = doOpenInterceptors(retryCallback, context);// 条件成立:有其中一个重试监听器的open方法返回了falseif (!running) {// 抛出异常throw new TerminatedRetryException("Retry terminated abnormally by interceptor before first attempt");}// Get or Start the backoff context...BackOffContext backOffContext = null;// 尝试从当前的重试上下文中获取退避上下文Object resource = context.getAttribute("backOffContext");if (resource instanceof BackOffContext) {backOffContext = (BackOffContext) resource;}// 条件成立:说明当前的重试上下文中没有设置退避上下文if (backOffContext == null) {// 这时候通过退避策略创建出对应的退避上下文backOffContext = backOffPolicy.start(context);// 再把这个退避上下文放到重试上下文中if (backOffContext != null) {context.setAttribute("backOffContext", backOffContext);}}// 条件成立:当前配置的重试策略允许重试,并且当前的重试上下文中没有设置中断重试的标志while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {try {if (this.logger.isDebugEnabled()) {this.logger.debug("Retry: count=" + context.getRetryCount());}// Reset the last exception, so if we are successful// the close interceptors will not think we failed...lastException = null;// 执行retryCallback,也就是执行目标重试方法return retryCallback.doWithRetry(context);}// 执行目标重试方法时抛异常了catch (Throwable e) {lastException = e;try {// 此时在重试上下文中记录下重试异常registerThrowable(retryPolicy, state, context, e);}catch (Exception ex) {throw new TerminatedRetryException("Could not register throwable", ex);}finally {// 遍历所有的重试监听器,执行其onError方法doOnErrorInterceptors(retryCallback, context, e);}// 在执行退避策略之前再判断一下是否还能重试if (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {try {// 执行退避策略backOffPolicy.backOff(backOffContext);}catch (BackOffInterruptedException ex) {lastException = e;// back off was prevented by another thread - fail the retryif (this.logger.isDebugEnabled()) {this.logger.debug("Abort retry because interrupted: count=" + context.getRetryCount());}throw ex;}}if (this.logger.isDebugEnabled()) {this.logger.debug("Checking for rethrow: count=" + context.getRetryCount());}if (shouldRethrow(retryPolicy, context, state)) {if (this.logger.isDebugEnabled()) {this.logger.debug("Rethrow in retry for policy: count=" + context.getRetryCount());}throw RetryTemplate.<E>wrapIfNecessary(e);}}/** A stateful attempt that can retry may rethrow the exception before now,* but if we get this far in a stateful retry there's a reason for it,* like a circuit breaker or a rollback classifier.*/if (state != null && context.hasAttribute(GLOBAL_STATE)) {break;}}// 代码执行到这里说明重试结束了if (state == null && this.logger.isDebugEnabled()) {this.logger.debug("Retry failed last attempt: count=" + context.getRetryCount());}exhausted = true;// 重试结束之后,最后执行recover方法,并返回recover方法的执行结果return handleRetryExhausted(recoveryCallback, context, state);}// 上面try中抛出异常之后catchcatch (Throwable e) {throw RetryTemplate.<E>wrapIfNecessary(e);}finally {close(retryPolicy, context, state, lastException == null || exhausted);// 执行所有重试监听器的close方法doCloseInterceptors(retryCallback, context, lastException);// 在ThreadLocal中清除当前的重试上下文,如有必要,还需把父级上下文设置回ThreadLocal中RetrySynchronizationManager.clear();}}

上面就是执行重试的核心流程代码,注释都详细写上去了,就不多说了。这里有个说一下的就是如果存在嵌套重试的话,我们需要去保存父层级的RetryContext,什么叫嵌套重试?就是在一个重试方法中调用了另一个重试方法,这两个重试方法的重试规则可能都不一样,这时候在执行第二个重试方法的时候就需要把第一个重试方法的RetryContext进行保存,那spring-retry是怎么保存的呢?在RetryContext中会有一个parent,这个parent记录的就是当前上一层的RetryContext,而当第二层重试执行完之后,这时候就会返回上一层的重试,所以就需要把上一层的RetryContext复原,这个复原的动作会在上面最后的finally代码块中执行。关联父子RetryContext的操作会在RetryPolicy的open方法中去执行,传入的参数就是父级的RetryContext

	/*** 获取一个重试上下文,不同的重试策略有自己的重试上下文** @param parent 父级重试上下文* @return a {@link RetryContext} object specific to this policy.**/RetryContext open(RetryContext parent);

相关文章:

深入了解Spring重试组件spring-retry

在我们的项目中&#xff0c;为了提高程序的健壮性&#xff0c;很多时候都需要有重试机制进行兜底&#xff0c;最多就场景就比如调用远程的服务&#xff0c;调用中间件服务等&#xff0c;因为网络是不稳定的&#xff0c;所以在进行远程调用的时候偶尔会产生超时的异常&#xff0…...

海南聚广众达电子商务咨询有限公司靠谱吗怎么样?

在当今这个数字化浪潮席卷全球的时代&#xff0c;抖音电商以其独特的魅力成为了众多商家争相入驻的新蓝海。而在这片浩瀚的电商海洋中&#xff0c;如何找到一家既专业又可靠的合作伙伴&#xff0c;成为了众多商家心中的一大难题。今天&#xff0c;我们就来深入剖析一下海南聚广…...

Java的魔法世界:面向对象编程(OOP)是什么?

这个嘎嘎重要 面向对象编程&#xff08;OOP&#xff09;是让Java像玩具世界一样&#xff0c;把现实中的东西变成“对象”&#xff0c;然后让这些对象去互动。你可以想象OOP是Java的“魔法世界”&#xff0c;通过创建“对象”&#xff08;Object&#xff09;&#xff0c;让它们有…...

软件测试笔记——接口测试

文章目录 一、概念1.接口测试流程2.URL3.HTTP协议4.RESTful5.案例介绍 二、Postman1.Postman软件2.登录接口调试-获取验证码3.登录接口调试-自动关联数据4.合同上传接口-提交请求数据5.提交参数查询6.批量执行7.接口用例设计8.断言8.参数化三、案例1.项目2.课程添加3.课程列表查…...

东方通 TongRDS V2 配置与开机自启指南及 Spring Boot 集成

东方通 TongRDS V2 配置与开机自启指南及 Spring Boot 集成 文章目录 东方通 TongRDS V2 配置与开机自启指南及 Spring Boot 集成一 简述二 配置 cfg.xml1 启用密码访问2 Spring Boot 连接 TongRDS 三 配置 TongRDS 开机自启1 配置 RdsCenter1&#xff09;设置 RdsCenter.servi…...

在 VS Code 中调试 Tensor 形状不显示的问题及解决方案

文章目录 常见问题解决方案1. 定制类包装和 __repr__ 方法 解释如何应用总结 在使用 VS Code 调试 PyTorch 代码时&#xff0c;可能会遇到一个常见问题&#xff1a;调试时 variables 窗口中不显示 Tensor 的形状信息。这会使得调试时观察数据的结构变得不便&#xff0c;尤其是在…...

Linux 时间获取全面总结

1. 引言 在Linux操作系统中&#xff0c;获取时间是一个基本且重要的功能。本文旨在全面总结Linux系统中获取时间的方法&#xff0c;包括命令行工具和编程接口&#xff0c;帮助读者深入理解Linux时间管理的机制。 2. 命令行工具 2.1 date 命令 date 命令是Linux中最常用的命…...

SQL 自学:游标(Cursors)的理解与应用

在 SQL 中&#xff0c;游标&#xff08;Cursor&#xff09;是一种用于处理从数据库中检索出的多行数据的机制。它允许我们逐行地处理查询结果集&#xff0c;而不是一次性处理整个结果集。 一、游标是什么 游标可以看作是一个指向结果集的指针。通过游标&#xff0c;我们可以在…...

IO多路复用概述与epoll简介

一、引言 在网络编程中&#xff0c;高并发的场景下处理大量连接请求是一项挑战。传统的阻塞式IO模型会让线程在等待数据的过程中陷入停顿&#xff0c;导致系统效率低下。为了解决这个问题&#xff0c;IO多路复用应运而生。它允许一个线程同时监听多个文件描述符&#xff08;如…...

关于region_to_label算子的想法

1&#xff0c;定义&#xff1a;将区域进行编码 2&#xff0c;如何做到的&#xff1a;底层逻辑应该是paint_region。通过一个小的循环&#xff0c;按顺序将区域从灰度值1开始11的往上喷。 3&#xff0c;有什么作用&#xff1a;目前能用到的&#xff0c;是有字典的作用&#xff0…...

uni-app 实现好看易用的抽屉效果

在移动应用开发中&#xff0c;抽屉效果是一种常用的用户界面设计&#xff0c;它能有效地节省空间&#xff0c;同时提供导航和其他功能。本文将介绍如何在uni-app中实现一个好看且易用的抽屉效果&#xff0c;帮助你提升应用的用户体验。 一、什么是抽屉效果&#xff1f; 抽屉效…...

PowerShell 脚本 比较两文件差异(带粗狂进度条)并汇总输出

一上来就放代码 function Compare-FileHex {param ([Parameter(Mandatory$true)][string]$SourceFile,[Parameter(Mandatory$true)][string]$CompareFile,[Parameter(Mandatory$false)][string]$OutputFile,[Parameter(Mandatory$false)][int]$BufferSize 1MB)function Forma…...

学习 UE5 的一些前置操作总结

随着 Unity, Godot 这些引擎都玩抽象&#xff0c;主动捅自己一刀后&#xff0c;UE5 的风头不可谓不盛&#xff0c;本着多学一点免得失业的思路方针&#xff0c;咱也研究了一下 UE5 引擎&#xff0c;然后发现想要开始使用 UE5 &#xff0c;包含了很多前置操作&#xff0c;这里总…...

C#/.NET/.NET Core技术前沿周刊 | 第 10 期(2024年10.14-10.20)

前言 C#/.NET/.NET Core技术前沿周刊&#xff0c;你的每周技术指南针&#xff01;记录、追踪C#/.NET/.NET Core领域、生态的每周最新、最实用、最有价值的技术文章、社区动态、优质项目和学习资源等。让你时刻站在技术前沿&#xff0c;助力技术成长与视野拓宽。 欢迎投稿、推荐…...

Git 基本配置

目录 打开 Git Bash设置用户信息查看配置信息修改电脑名字为常用指令配置别名打开用户目录&#xff0c;创建 .bashrc 文件在 .bashrc 文件中输入如下内容&#xff1a;打开gitBash&#xff0c;执行 source ~/.bashrc 解决GitBash乱码问题打开GitBash执行下面命令${git_home}/etc…...

理工科考研想考计算机,湖南大学、重大、哈工大威海、山东大学,该如何选择?

C哥专业提供——计软考研院校选择分析专业课备考指南规划 计算机对理工科同学来说&#xff0c;还是性价比很高的&#xff0c;具有很大的优势&#xff01; 一、就业前景广阔 高需求行业 在当今数字化时代&#xff0c;计算机技术几乎渗透到了各个领域&#xff0c;无论是互联网…...

使用langchain和大模型API提取QA的实战教程

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于大模型算法的研究与应用。曾担任百度千帆大模型比赛、BPAA算法大赛评委,编写微软OpenAI考试认证指导手册。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。授权多项发明专利。对机器学…...

Java面试场景题(1)---如何使用redis记录上亿用户连续登陆天数

感谢uu们的观看&#xff0c;话不多说开始~ 对于这个问题&#xff0c;我们需要先来了解一下~ 海量数据都可以用bitmap来存储&#xff0c;因为占得内存小&#xff0c;速度也很快 我大概计算了一下~ 完全够&#xff1a;String类型512M 1byte 8个bit位 8个状态 512M1024byt…...

Element UI

Element ui 就是基于vue的一个ui框架,该框架基于vue开发了很多相关组件,方便我们快速开发页面。 官网: https://element.eleme.io/#/zh-CN 安装Element UI vue init webpack element(项目名)确认项目是否构建成功&#xff1a;进入到项目的根路径 执行 npm start 访问 h…...

②PROFINET转ModbusTCP, EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关

EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关https://item.taobao.com/item.htm?ftt&id822721028899 协议转换通信网关 PROFINET 转 Modbus TCP &#xff08;接上一章&#xff09; 配置使用 与 PROFINET 主站进行组态说明 这里介绍与西门子 PLC 的…...

终极指南:GitHub加速计划testing-samples测试工具链——从开发到部署的全流程自动化测试方案

终极指南&#xff1a;GitHub加速计划testing-samples测试工具链——从开发到部署的全流程自动化测试方案 【免费下载链接】testing-samples A collection of samples demonstrating different frameworks and techniques for automated testing 项目地址: https://gitcode.co…...

SEO_ 揭秘影响搜索引擎排名的核心因素与算法

SEO核心因素解析&#xff1a;揭秘影响搜索引擎排名的算法 在互联网时代&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;已成为每一个网站运营者的重要关注点。SEO不仅关系到网站的流量&#xff0c;更直接影响到网站的知名度和商业价值。究竟有哪些核心因素和算法影响着搜…...

Fish-Speech-1.5语音合成参数详解:从基础到高级

Fish-Speech-1.5语音合成参数详解&#xff1a;从基础到高级 语音合成技术已经发展到了一个令人惊叹的水平&#xff0c;而Fish-Speech-1.5作为当前领先的文本转语音模型&#xff0c;提供了丰富的参数调节选项&#xff0c;让用户能够精准控制合成语音的风格和效果。无论你是刚接…...

# 005、通信栈深度解析:COM、PDU Router与网络管理

从一次诡异的网络丢包说起 上个月在客户现场蹲到凌晨三点,问题现象是ECU在总线唤醒后前两帧数据总是丢。示波器抓波形完全正常,Trace看PDU也的确发到了总线上,可对端节点就是收不到。熬到后半夜,盯着AUTOSAR配置工具里那几十个PDU路由表,突然意识到问题出在PDU Router的时…...

Excel实战:手把手教你用条件格式和分类汇总分析个人开支(计算机二级考点全覆盖)

Excel实战&#xff1a;手把手教你用条件格式和分类汇总分析个人开支&#xff08;计算机二级考点全覆盖&#xff09; 在个人财务管理中&#xff0c;Excel是最基础也最强大的工具之一。无论是备考计算机二级的考生&#xff0c;还是希望提升工作效率的职场人士&#xff0c;掌握Exc…...

AI摄影师助手:OpenClaw调用Qwen3-32B自动筛选与修图

AI摄影师助手&#xff1a;OpenClaw调用Qwen3-32B自动筛选与修图 1. 从手动修图到AI助手的转变 作为一名摄影爱好者&#xff0c;我经常面临一个令人头疼的问题&#xff1a;每次拍摄结束后&#xff0c;相机里堆积如山的RAW文件需要花费大量时间筛选和后期处理。直到上个月&…...

OpenClaw云端体验指南:无需本地安装快速测试Phi-3-vision-128k-instruct

OpenClaw云端体验指南&#xff1a;无需本地安装快速测试Phi-3-vision-128k-instruct 1. 为什么选择云端体验OpenClaw 作为一个长期折腾本地AI部署的技术爱好者&#xff0c;我完全理解那种"想先试试再决定是否投入"的心态。去年尝试在MacBook Pro上部署Llama 2时&am…...

STM32一键下载电路设计与实现

1. STM32一键下载电路解析作为一名嵌入式开发者&#xff0c;我深知每次烧录程序都要手动切换BOOT0和复位键的痛苦。正点原子的这个一键下载电路设计确实巧妙&#xff0c;让我们来深入剖析它的工作原理。这个电路的核心在于利用CH340芯片的DTR#和RTS#信号&#xff0c;通过三极管…...

OpenClaw v2026.3.31 深度解读:为什么这次更新不是“小修小补”,而是一次明显的安全收口与后台任务体系成形

&#x1f525;个人主页&#xff1a;杨利杰YJlio❄️个人专栏&#xff1a;《Sysinternals实战教程》《Windows PowerShell 实战》《WINDOWS教程》《IOS教程》《微信助手》《锤子助手》 《Python》 《Kali Linux》《那些年未解决的Windows疑难杂症》&#x1f31f; 让复杂的事情更…...

网络协议封神考点:TCP协议是如何保证可靠传输的?原理+流程图+硬核详解

网络协议封神考点&#xff1a;TCP协议是如何保证可靠传输的&#xff1f;原理流程图硬核详解一、前言二、基础定义&#xff1a;什么是TCP可靠传输&#xff1f;三、TCP保证可靠传输的6大核心机制&#xff08;必考&#xff09;3.1 机制1&#xff1a;面向连接&#xff08;三次握手 …...