深入了解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);

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

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

相关文章

这几次比赛题解

因为考虑到再看&#xff0c;所以将所有题目都做成了pdf格式 梦熊十三连测 T1 这道题其实什么也不用想&#xff0c;就按照题目给的意思来打代码就行&#xff0c;这就有40分可以拿。懒人做法 #include<bits/stdc.h> using namespace std; typedef long long ll; ll read…

MP9928模块分析

MP9928 是一款高性能的同步降压 DC/DC 转换器控制器 IC&#xff0c;具有宽输入范围。以下是其操作和关键特性的总结&#xff1a; 概述 电流模式控制&#xff1a;MP9928 使用电流模式、可编程开关频率控制架构&#xff0c;通过外部 N 沟道 MOSFET 开关来调节输出电压。 反馈和…

Golang | Leetcode Golang题解之第500题键盘行

题目&#xff1a; 题解&#xff1a; func findWords(words []string) (ans []string) {const rowIdx "12210111011122000010020202" next:for _, word : range words {idx : rowIdx[unicode.ToLower(rune(word[0]))-a]for _, ch : range word[1:] {if rowIdx[unico…

【uni-app学习-2】

一、跳转 方法&#xff1a;在methods中去定义方法&#xff1a; 上述为直接跳转&#xff0c;但是当你要跳转页面是由多个可切换页面组成比如&#xff1a; 这个页面其实是由两个页面组成&#xff0c;一个主页&#xff0c;一个我的&#xff0c;两个页面 路由配置需要用到toob…

房屋租赁网站毕业设计基于SpringBootSSM框架的计算机毕业设计

计算机毕业设计/springboot/javaWEB/J2EE/MYSQL数据库/vue前后分离小程序 目录 一、项目背景与目的‌ ‌二、系统需求分析‌ 2.1功能需求 2.2 技术需求 2.3 可执行性 ‌三、系统设计与实现‌ ‌3.1系统架构设计‌&#xff1a; ‌3.2功能模块开发‌&#xff1a; ‌3.3…

golang生成并分析cpu prof文件

1. 定义一个接口&#xff0c;请求接口时&#xff0c;生成cpu.prof文件 在主协程中新启一个协程&#xff0c;当请求接口时&#xff0c;生成一个60秒的cpu.prof文件 go func() {http.HandleFunc("/prof", startProfileHandler)http.ListenAndServe(":9092"…

Spring Boot助力:构建响应式论坛网站

3系统分析 3.1可行性分析 通过对本论坛网站实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本论坛网站采用SSM框架&#xff0c;JAVA作为开发语言&#xff0c;是…

华为云CodeArts Pipeline架构与内容双重优化,高效助力持续交付!

点击下方链接进入帮助中心 成长地图_流水线 CodeArts Pipeline_华为云

unity学习-全局光照(GI)

在全局光照&#xff08;Lighting&#xff09;界面有两个选项 Realtime Light&#xff08;实时光照&#xff09;&#xff1a;在项目中会提前计算好光照以及阴影的程序&#xff0c;当你需要调用实时全局光照的时候会将程序调用出来使用 Mixed Light&#xff08;烘焙光照&#x…

HBuilder X 中Vue.js基础使用1(三)

一、 模板语法 Vue 使用一种基于 HTML 的模板语法&#xff0c;使我们能够声明式地将其组件实例的数据绑定到呈现的 DOM 上。所有的 Vue 模板都是语法层面合法的 HTML&#xff0c;可以被符合规范的浏览器和 HTML 解析器解析。 英文官网: Vue.js - The Progressive JavaScript Fr…

DPRNN 学习

DPRNN介绍 双路径循环语音分离神经网络&#xff08;Dual-Path RNN&#xff09;由三个处理阶段组成, 编码器、分离和解码器。首先&#xff0c;编码器模块用于将混合波形的短段转换为它们在中间特征空间中的对应表示。然后&#xff0c;该表示用于在每个时间步估计每个源的乘法函…

HCIP-HarmonyOS Application Developer 习题(十四)

&#xff08;多选&#xff09;1、HarmonyOs为应用提供丰富的Al(Artificial Intelligence)能力&#xff0c;支持开箱即用。下列哪些是它拥有的AI能力? A、通用文字识别 B、词性标注 C、实体识别 D、语音播报 答案&#xff1a;ABCD 分析&#xff1a; AI能力简介二维码生成根据开…

(JAVA)贪心算法、加权有向图与求得最短路径的基本论述与实现

1. 贪心算法 1.1 贪心算法的概述&#xff1a; 贪心算法是一种对某些求最优解问题的更简单、更迅速的设计技术。 贪心算法的特点是一步一步地进行&#xff0c;常以当前情况为基础根据某个优化测度作最优选择&#xff0c;而不考虑各种可能的整体情况&#xff0c;省去了为找最优…

【深度学习中的注意力机制6】11种主流注意力机制112个创新研究paper+代码——加性注意力(Additive Attention)

【深度学习中的注意力机制6】11种主流注意力机制112个创新研究paper代码——加性注意力&#xff08;Additive Attention&#xff09; 【深度学习中的注意力机制6】11种主流注意力机制112个创新研究paper代码——加性注意力&#xff08;Additive Attention&#xff09; 文章目录…

【C#】调用本机AI大模型流式返回

【python】AI Navigator的使用及搭建本机大模型_anaconda ai navigator-CSDN博客 【Python】AI Navigator对话流式输出_python ai流式返回-CSDN博客 前两章节我们讲解了使用AI Navigator软件搭建本机大模型&#xff0c;并使用python对大模型api进行调用&#xff0c;使其流式返…

“智能科研写作:结合AI与ChatGPT提升SCI论文和基金申请质量“

基于AI辅助下的高效高质量SCI论文撰写及投稿实践 科学研究的核心在于将复杂的思想和实验成果通过严谨的写作有效地传递给学术界和工业界。对于研究生、青年学者及科研人员&#xff0c;如何高效撰写和发表SCI论文&#xff0c;成为提升学术水平和科研成果的重要环节。系统掌握从…

SAP_FICO模块-资产减值功能对折旧和残值的影响

一、业务背景 由于财务同事没注意&#xff0c;用总账给资产多做了一笔凭证&#xff0c;导致该资产金额虚增&#xff0c;每个月的折旧金额也虚增&#xff1b;现在财务的需求是怎么操作可以进行资产减值&#xff0c;并且减少每个月计提的折旧&#xff1b; 二、实现方式 通过事务码…

qt EventFilter用途详解

一、概述 EventFilter是QObject类的一个事件过滤器&#xff0c;当使用installEventFilter方法为某个对象安装事件过滤器时&#xff0c;该对象的eventFilter函数就会被调用。通过重写eventFilter方法&#xff0c;开发者可以在事件处理过程中进行拦截和处理&#xff0c;实现对事…

go 语言 Gin Web 框架的实现原理探究

Gin 是一个用 Go (Golang) 编写的 Web 框架&#xff0c;性能极优&#xff0c;具有快速、支持中间件、crash处理、json验证、路由组、错误管理、内存渲染、可扩展性等特点。 官网地址&#xff1a;https://gin-gonic.com/ 源码地址&#xff1a;https://github.com/gin-gonic/gi…

Shell重定向输入输出

我的后端学习大纲 我的Linux学习大纲 重定向介绍 标准输入介绍 从键盘读取用户输入的数据&#xff0c;然后再把数据拿到Shell程序中使用&#xff1b; 标准输出介绍 Shell程序产生的数据&#xff0c;这些数据一般都是呈现到显示器上供用户浏览查看; 默认输入输出文件 每个…