深入了解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,一经查实,立即删除!

相关文章

7. 配置

三种获取配置的方法 返回 /config/config.php 、/config/autoload/xxx.php 中的值 <?php namespace App\Controller;use Hyperf\Config\Annotation\Value; use Hyperf\Contract\ConfigInterface; use Hyperf\Di\Annotation\Inject; use Hyperf\HttpServer\Annotation\AutoC…

Vue应用中使用xlsx库实现Excel文件导出的完整指南

Vue应用中使用xlsx库实现Excel文件导出的完整指南 在现代Web开发中&#xff0c;经常需要将数据导出为Excel文件&#xff0c;以便于用户进行离线分析或记录。Vue.js作为一个轻量级且高效的前端框架&#xff0c;结合xlsx库可以轻松实现这一功能。本文将详细介绍如何在Vue应用中使…

这几次比赛题解

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

python实现机器狗的行动控制

import sys import timefrom fontTools.merge import timer from ipykernel.heartbeat import Heartbeatdef OdomCallback(msg):print("腿部里程计调用")global body_x, body_yclass Move_control():def __init__(self):# 连接机器狗的运动主机及其端口self.server_a…

(二)Java1.8核心包rt.jar——java.lang.instrument

1、ClassFileTransformer /** * 参数: * loader - 正在加载类的类加载器 * className - 正在加载的类的名称 * classBeingRedefined -重定义的类 * protectionDomain-要定义或重定义的类的保护域 * classfileBuffer-类文件格式的输入字节缓冲区(不得修改) */ 1 public interfa…

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"…

WebScoket 使用教程

一、简介&#xff1a; WebSocket是一种协议&#xff0c;用于在Web应用程序和服务器之间建立实时、双向的通信连接。它通过一个单一的TCP连接提供了持久化连接&#xff0c;这使得Web应用程序可以更加实时地传递数据。WebSocket协议最初由W3C开发&#xff0c;并于2011年成为标准…

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

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

Windows、macOS和Linux系统安装Python的指南

在不同的操作系统上安装Python通常涉及几个简单的步骤。以下是在Windows、macOS和Linux系统上安装Python的指南&#xff1a; Windows系统安装Python&#xff1a; 下载Python安装程序&#xff1a; 访问Python官方网站 python.org。选择适合Windows的Python版本&#xff08;确保…

xtu oj 最多的可变换字符串

文章目录 回顾解题思路c 语言代码 回顾 AB III问题 H: 三角数问题 G: 3个数等式 数组下标查询&#xff0c;降低时间复杂度1405 问题 E: 世界杯xtu 数码串xtu oj 神经网络xtu oj 1167 逆序数&#xff08;大数据&#xff09;xtu oj 原根xtu oj 不定方程的正整数解 解题思路 冒…

git区分大小写吗?如果不区分,那要如何设置?

git区分大小写吗&#xff1f;如果不区分&#xff0c;那要如何设置&#xff1f; "Git在文件名的大小写方面是区分大小写的&#xff0c;但在某些操作系统&#xff08;如Windows和macOS&#xff09;上&#xff0c;文件系统默认是不区分大小写的。这可能导致一些问题&#xf…

华为云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;该表示用于在每个时间步估计每个源的乘法函…

力扣10.19

3192. 使二进制数组全部等于 1 的最少操作次数 II 给你一个二进制数组 nums 。 你可以对数组执行以下操作 任意 次&#xff08;也可以 0 次&#xff09;&#xff1a; 选择数组中 任意 一个下标 i &#xff0c;并将从下标 i 开始一直到数组末尾 所有 元素 反转 。 反转 一个元…