Spring retry(一)-使用指南

news2024/11/26 12:50:44

一、接入

spring boot 2.7.14

spring retry 从2.0.2版本之后,从spring batch里剥离出来成为一个单独的工程,因此我们引入spring retry最新版本可以直接如下引入

<dependency>
   <groupId>org.springframework.retry</groupId>
   <artifactId>spring-retry</artifactId>
   <version>2.0.2</version>
</dependency>

<dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>1.9.7</version>
</dependency>

启动类上打上注解@EnableRetry
image.png

二、使用注解

Spring retry作为重试组件,可以直接使用@Retryable注解;废话不多说,直接上代码

@Component
public class UserService {


    @Retryable(retryFor = BizException.class, maxAttempts = 5, backoff = @Backoff(delay = 1000L, multiplier=1))
    public int service(int throwErr) throws BizException {

        System.out.println("===== service =====" + DateUtil.getDateTime(new Date()));

        if (throwErr==1) {
            throw new BizException();
        }

        return 1;
    }
}

执行效果如下

image.png

retryFor指定异常进行重试,如果不指定的话,默认任何异常都会重试;
maxAttempts重试次数,默认值是3次;
backoff是用于控制延迟重试策略,@Backoff(delay = 1000L, multiplier=2)表示每次执行失败,再次延迟时间=上次延迟时间*2

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retryable {
    //最终失败情况下,调用recover进行恢复
    String recover() default "";

    //拦截器,主要是aop切面逻辑,具体待验证, 目前看下来主要是配合stateful一起使用,带验证
    String interceptor() default "";


    //包含哪些异常
    @AliasFor("include")
    Class<? extends Throwable>[] retryFor() default {};

    //哪些异常不重试
    @AliasFor("exclude")
    Class<? extends Throwable>[] noRetryFor() default {};

    //哪些异常不作回滚
    Class<? extends Throwable>[] notRecoverable() default {};

    //标签,没啥意义
    String label() default "";
   
    //有状态的重试,这个我们单独开讲
    boolean stateful() default false;

    //最大重试次数
    int maxAttempts() default 3;
    
    //最大重试次数表达式
    String maxAttemptsExpression() default "";

    //延迟策略
    Backoff backoff() default @Backoff;

    //异常过滤
    String exceptionExpression() default "";
    
    //监听器
    String[] listeners() default {};
}

三、使用RetryTemplate

上面通过注解@Retryable(retryFor = BizException.class, maxAttempts = 5, backoff = @Backoff(delay = 1000L, multiplier=1)) 的效果,可以通过如下编码的形式来实现

public int service2(int throwErr) throws BizException {
    RetryTemplate template = RetryTemplate.builder()
            .maxAttempts(3)
            .retryOn(BizException.class)
            .customBackoff(
                    BackOffPolicyBuilder
                            .newBuilder()
                            .delay(1000)
                            .multiplier(2)
                            .build()

            )
            .build();

    return template.execute(ctx->{
        System.out.println("===== service =====" + DateUtil.getDateTime(new Date()));

        if (throwErr==1) {
            throw new BizException("system error");
        }

        return 1;
    });
}

常见重试策略:

策略类效果关键参数
MaxAttemptsRetryPolicy设置最大的重试次数,超过之后执行recovermaxAttempts:最大重试次数
BinaryExceptionClassifierRetryPolicy可以指定哪些异常需要重试,哪些异常不需要重试exceptionClassifier:BinaryExceptionClassifier,异常识别类,其实就是存在一个Map映射
SimpleRetryPolicy上面两者的结合,支持次数和自定义异常重试maxAttempts 和 exceptionClassifier
TimeoutRetryPolicy在指定的一段时间内重试timeout:单位ms
ExceptionClassifierRetryPolicy支持异常和重试策略的映射,比如异常A对应重试策略A,异常B对应重试策略BexceptionClassifier:本质就是异常和重试策略的映射
CompositeRetryPolicy策略类的组合类,支持两种模式,乐观模式:只要一个重试策略满足即执行,悲观模式:另一种是所有策略模式满足再执行policies:策略数组optimistic:true-乐观;false-悲观
CircuitBreakerRetryPolicy熔断器模式delegate:策略代理类openTimeout:[0,openTimeout]会一直执行代理类策略,(openTimeout, resetTimeout] 会半打开,如果代理类判断不可以重试,就会熔断,执行recover逻辑,如果代理类判断还可以重试且重试成功,开关会闭合。
resetTimeout:超过指定时间,开关重置闭合
ExpressionRetryPolicy符合表达式就重试expression:表达式,见 org.springframework.expression.Expression实现

常见回退策略:

策略类效果关键参数
FixedBackOffPolicy间隔固定时间重试sleeper:支持线程sleep和Object#wait,区别在于是否释放锁backOffPeriod:等待间隔
NoBackOffPolicy无等待直接重试
ExponentialBackOffPolicy在一个设置的时间区间内,等待时长为上一次时长的递增initialInterval:默认起始等待时间
maxInterval:最大等待时间
multiplier:递增倍数
sleeper:同上
ExponentialRandomBackOffPolicy在 ExponentialBackOffPolicy 基础上,乘数随机

四、关于RetryContentxt

普通场景下,重试是不需要获取之前重试的状态的,但是某些场景下,每次重试可能都需要打印当前重试次数,并且塞进去相关信息等

template.execute(ctx->{
    System.out.println("===== service =====" + DateUtil.getDateTime(new Date()));
    //上下文获取当前重试次数
    System.out.println("retry count="+ctx.getRetryCount());
    //也可以塞一些东西进去
    System.out.println("retry count="+ctx.getAttribute("attr"))
    //从属性里取值
    ctx.setAttribute("attr", "test"+ctx.getRetryCount());
    
    //直接终止当前重试,这个比较牛逼,配合固定重试次数来搞
    ctx.setExhaustedOnly()

    if (throwErr==1) {
        throw new BizException("system error");
    }

    return 1;
});

五、重试策略&延迟重试

我们直接把重试策略

public interface RetryPolicy extends Serializable {

   /**
    * @param context the current retry status
    * @return true if the operation can proceed
    */
   boolean canRetry(RetryContext context);

   /**
    * Acquire resources needed for the retry operation. The callback is passed in so that
    * marker interfaces can be used and a manager can collaborate with the callback to
    * set up some state in the status token.
    * @param parent the parent context if we are in a nested retry.
    * @return a {@link RetryContext} object specific to this policy.
    *
    */
   RetryContext open(RetryContext parent);

   /**
    * @param context a retry status created by the {@link #open(RetryContext)} method of
    * this policy.
    */
   void close(RetryContext context);

   /**
    * Called once per retry attempt, after the callback fails.
    * @param context the current status object.
    * @param throwable the exception to throw
    */
   void registerThrowable(RetryContext context, Throwable throwable);

}

image.png
从上图我们可以看到很多重试策略的实现,

六、recover

retry组件重试最终失败后,会调用recover方法(有点像回滚)

@Component
public class TestService {

    @Retryable(retryFor = RemoteAccessException.class)
    public void service() {
        System.out.println("===== service =====" + DateUtil.getDateTime(new Date()));
        throw new RemoteAccessException("xx");
    }
    @Recover
    public void recover(RemoteAccessException e) {
        System.out.println("===== recover =====" + DateUtil.getDateTime(new Date()));

    }

}

image.png

七、监听器Listeners

参考接口:

public interface RetryListener {

    void open(RetryContext context, RetryCallback<T> callback);

    void onSuccess(RetryContext context, T result);

    void onError(RetryContext context, RetryCallback<T> callback, Throwable e);

    void close(RetryContext context, RetryCallback<T> callback, Throwable e);

}

实现如下:

@Bean("listener1")
public RetryListener getListener() {
    return new RetryListener() {
        @Override
        public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {

            System.out.println("===== close =====");
        }

        @Override
        public <T, E extends Throwable> void onSuccess(RetryContext context, RetryCallback<T, E> callback, T result) {
            System.out.println("===== close =====");
        }
    };
}

然后在注解上引用

@Retryable(retryFor = RemoteAccessException.class, maxAttempts = 4,
        backoff = @Backoff(delay = 1000L, multiplier=2),
        listeners = {"listener1"}
)
public void service() {
    System.out.println("===== service =====" + DateUtil.getDateTime(new Date()));
    throw new RemoteAccessException("xx");
}

最终执行效果如下

image.png

八、有状态的重试stateful

有状态重试通常是用在message-driven 的应用中,从消息中间件比如RabbitMQ等接收到的消息,如果应用处理失败,那么消息中间件服务器会再次投递,再次投递时,对于集成了Spring Retry的应用来说,再次处理之前处理失败的消息,就是一次重试;也就是说,Spring Retry能够识别出,当前正在处理的消息是否是之前处理失败过的消息;

如果是之前处理过的消息,Spring Retry就可以使用 back off policies 阻塞当前线程;Spring Retry同时追踪重试的次数,支持处理彻底失败后的recover,这也是使用有状态重试的理由;

有状态重试的另一个典型应用场景是跟Spring Transaction框架集成。在集成了Spring Transaction框架的MVC应用中,通过TransactionInterceptor,开启对Service层的事务管理;在这种情况下,Spring Retry会提供让每一次重试和重试次数耗尽之后的recover都在一个新的事务中执行。

九、retry组件的忧缺点

整个使用下来,retry组件的优点:

  1. 无侵入式的实现了重试,大大减小了重试代码成本
  2. 重试策略比较灵活,支持固定频率重试、延迟重试等策略

缺点:

  1. 不支持异步重试,且重试过程是阻塞当前程序的,当然,如果要实现异步重试,需要配合@Async注解来搞

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

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

相关文章

力扣92. 局部反转链表

92. 反转链表 II 给你单链表的头指针 head 和两个整数 left 和 right &#xff0c;其中 left < right 。请你反转从位置 left 到位置 right 的链表节点&#xff0c;返回 反转后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], left 2, right 4 输出&am…

mysql数据库备份还原-mydumper/myloader

目录 一、mydumper介绍1、 mydumper优点2、 mydumper缺点3、工作原理4、备份所生成的文件 二、 mydumper 安装1.编译安装2.直接安装 rpm 包 三、 mydumper 使用1、参数说明2、mydumper用法 四、 myloader 使用1、参数说明2、myloader用法 对比mysqldump、mysqlpump、mydumper和…

2023年9月重庆/南京/深圳CDGA/CDGP数据治理认证考试报名

据DAMA中国官方网站消息&#xff0c;2023年度第三期DAMA中国CDGA和CDGP认证考试定于2023年9月23日举行。 报名通道现已开启&#xff0c;相关事宜通知如下&#xff1a; 考试科目: 数据治理工程师(CertifiedDataGovernanceAssociate,CDGA) 数据治理专家(CertifiedDataGovernanc…

最大正方形

题目链接 最大正方形 题目描述 注意点 matrix[i][j] 为 ‘0’ 或 ‘1’ 解答思路 使用动态规划解决本题&#xff0c;任意一个格子作为正方形右下角时&#xff0c;其最大正方向面积取决于左侧&#xff0c;上方以及左上角三个格子对应的正方形边长最小值E1&#xff0c;改格子…

直播预约|哪吒汽车岳文强:OEM和Tier1如何有效对接网络安全需求

信息安全是一个防护市场。如果数字化程度低&#xff0c;数据量不够&#xff0c;对外接口少&#xff0c;攻击成本高&#xff0c;所获利益少&#xff0c;自然就没有什么攻击&#xff0c;车厂因此也不需要在防护上花费太多成本。所以此前尽管说得热闹&#xff0c;但并没有太多真实…

xml转json

XML有个坑&#xff0c;就是XML转JSON中如何把单个元素转成数组&#xff0c;导致映射到实体类时无法执行&#xff0c;问题如下 初始xml格式如下&#xff1a; <java><version>1.8</version> </java>上述转成json格式就是&#xff1a; {"java&quo…

学习记录——Efficient MOdel轻量化主干模型(iRMB、EMO)

Rethinking Mobile Block for Efficient Attention-based Models 结合 CNN 和 Transformer 的倒残差移动模块设计 ICCV-2023 实例化了一个面向移动端应用的iRMB基础模块&#xff08;Inverted Residual Mobile Block&#xff0c;倒残差移动模块&#xff09;&#xff0c;其同时具…

UI自动化面试题合集

1、什么是UI自动化测试&#xff1f; UI自动化测试是一种通过模拟用户交互并自动执行UI操作的软件测试方法。它用于验证用户界面的功能和稳定性&#xff0c;以确保在不同的操作系统、浏览器和设备上的一致性。 同时&#xff0c;在这我也准备了一份软件测试面试视频教程&#x…

怎么消除人声保留背景音乐?试试这几种简单方法

消除人声保留背景音乐可以用于许多不同的目的。例如&#xff0c;可以在视频制作中使用&#xff0c;以确保观众能够听到清晰的对话&#xff0c;而不会被其他噪音干扰。此外&#xff0c;它也可以用于音乐制作中&#xff0c;以便更好地混合和控制音频元素。教大家几种简单的提取方…

白鲸开源 DataOps 平台加速数据分析和大模型构建

作者 | 李晨 编辑 | Debra Chen 数据准备对于推动有效的自助式分析和数据科学实践至关重要。如今&#xff0c;企业大都知道基于数据的决策是成功数字化转型的关键&#xff0c;但要做出有效的决策&#xff0c;只有可信的数据才能提供帮助&#xff0c;随着数据量和数据源的多样…

【大虾送书第七期】深入浅出SSD:固态存储核心技术、原理与实战

目录 ✨写在前面 ✨内容简介 ✨作者简介 ✨名人推荐 ✨文末福利 &#x1f990;博客主页&#xff1a;大虾好吃吗的博客 &#x1f990;专栏地址&#xff1a;免费送书活动专栏地址 写在前面 近年来国家大力支持半导体行业&#xff0c;鼓励自主创新&#xff0c;中国SSD技术和产业…

Java Stream与多线程

Java Stream 1. 问题引入 学习了尚硅谷的JUC&#xff0c;周阳老师讲的商城比较价格的案例&#xff0c;自己模拟了一个多线程的demo, 多线程处理任务并且汇总结果&#xff0c;出现了疑问&#xff0c;实例代码放在下面&#xff0c;读者有兴趣的话可ctrlcv玩一玩 如下是自定义的任…

BEV感知算法的概念

本文来自自动驾驶之心知识星球的国内首个BEV感知全栈系列学习教程 什么是BEV&#xff1f; ----> 上帝视角 Bird’s-Eye-View&#xff0c;鸟瞰图&#xff08;俯视图&#xff09;尺度变化小 &#xff08;离相机远的尺度比较小&#xff0c;离相机近的尺度比较大&#xff0c;比…

RT_Thread内核机制学习(四)队列

队列 队列中每个消息块都有一个头部&#xff0c;指向下一个消息块。 消息块的内存是连在一起的&#xff0c;但是是用链表组织的。 struct rt_messagequeue {struct rt_ipc_object parent; /**< inherit from ipc_object */void *m…

《独立开发者首次飞行指南》终于来了!!

大家好&#xff0c;我是彭涛。 现在&#xff0c;每年都有各家大厂裁员&#xff0c;各类中小厂跑路&#xff0c;失业人数越来越多的新闻。如果&#xff0c;我们身背房贷&#xff0c;我们应该都会非常焦虑。大环境下&#xff0c;我们不得不逐一探索新的赚钱之道&#xff0c;前段时…

anaconda环境迁移

conda环境迁移第一步 进入anaconda安装文件夹&#xff0c;然后进入envs文件夹&#xff0c;下面的每一个文件夹都是你创建的环境&#xff0c; 准备一个u盘之类的&#xff0c;把整个文件夹复制下来&#xff0c;然后打开另外一台机器&#xff0c;把同样的文件夹复制到同样的文件夹…

JS设置视频播放速度

方法 一&#xff1a;示例代码 document.querySelector(video).playbackRate 5; 进入到要加速的视频页面按F12打开控制控制台输入代码并回车 方法二&#xff1a;示例代码 document.getElementsByTagName("video")[0].playbackRate 5; 进入到要加速的视频页面按F…

钉钉消息已读、未读咋实现的嘞?

前言 一款app&#xff0c;消息页面有&#xff1a;钱包通知、最近访客等各种通知类别&#xff0c;每个类别可能有新的通知消息&#xff0c;实现已读、未读功能&#xff0c;包括多少个未读&#xff0c;这个是怎么实现的呢&#xff1f;比如用户A访问了用户B的主页&#xff0c;难道…

Java实现获取微信小程序scheme码报错

如标题所见&#xff0c;使用Java获取小程序scheme时除了出现文档中的常见错误&#xff0c;我将我调试的时候遇到的错误和解决方式分享出来方便大家少花一部分时间解决该问题。&#xff08;往下划有结论节省时间&#xff09;。 获取scheme码之前需要先获取access_token&#xff…

Vue生命周期(详细)

生命周期 图&#xff1a; 可以理解vue生命周期就是指vue实例从创建到销毁的过程&#xff0c;在vue中分为8个阶段&#xff1a;创建前/后&#xff0c;载入前/后&#xff0c;更新前/后&#xff0c;销毁前/后。 一、创建&#xff08;实例&#xff09; 1、beforeCreate&#xff1a…