重试框架入门:Spring-RetryGuava-Retry

前言

在日常工作中,随着业务日渐庞大,不可避免的涉及到调用远程服务,但是远程服务的健壮性和网络稳定性都是不可控因素,因此,我们需要考虑合适的重试机制去处理这些问题,最基础的方式就是手动重试,侵入业务代码去处理,再高端一点的通过切面去处理,较为优雅的实现重试,下面,介绍两个重试框架,只需要配置好重启策略及重试任务,即可使用。

重试任务

这里只是模拟传参、相应及异常,具体任务需对应业务

package com.example.test.MessageRetry;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.remoting.RemoteAccessException;

@Slf4j
public class RetryTask {
    /**
     * 重试方法
     * @param param
     * @return
     */
    public static boolean retryTask(String param){
        log.info("请求参数:{}",param);
        int i = RandomUtils.nextInt(0,15);
        log.info("随机数:{}",i);
        if(i == 0){
            log.error("参数异常");
            throw new IllegalArgumentException("参数异常");
        }else if(i > 10){
            log.error("访问异常");
            throw new RemoteAccessException("访问异常");
        }else if(i % 2 == 0){
            log.info("成功");
            return true;
        }else{
            log.info("失败");
            return false;
        }
    }
}

Spring-Retry

简介

Spirng-Retry是Spring提供的一个重试框架,为spring程序提供声明式重试支持,主要针对可能抛出异常的调用操作,进行有策略的重试。可以通过代码方式和注解方式实现,主要由重试执行者和两个策略构成:

  • RetryTemplet:重试执行者,可以设置重试策略(设置重试上线、如何重试)和回退策略(立即重试还是等待一段时间后重试,默认立即重试,如果需要配置等待一段时间后重试则需要指定回退策略),通过Execute提交执行操作,只有在调用时抛出指定配置的异常,才会执行重试
  • 重试策略:
策略方式
NeverRetryPolicy只允许调用RetryCallback一次,不允许重试
AlwaysRetryPolicy允许无限重试,直到成功,此方式逻辑不当会导致死循环
SimpleRetryPolicy固定次数重试策略,默认重试最大次数为3次,RetryTemplate默认使用的策略
TimeoutRetryPolicy超时时间重试策略,默认超时时间为1秒,在指定的超时时间内允许重试
ExceptionClassifierRetryPolicy设置不同异常的重试策略,类似组合重试策略,区别在于这里只区分不同异常的重试
CircuitBreakerRetryPolicy有熔断功能的重试策略,需设置3个参数openTimeout、resetTimeout和delegate
CompositeRetryPolicy组合重试策略,有两种组合方式,乐观组合重试策略是指只要有一个策略允许即可以重试,悲观组合重试策略是指只要有一个策略不允许即可以重试,但不管哪种组合方式,组合中的每一个策略都会执行
  • 重试回退策略:
回退策略方式
NoBackOffPolicy无退避算法策略,每次重试时立即重试
FixedBackOffPolicy固定时间的退避策略,需设置参数sleeper和backOffPeriod,sleeper指定等待策略,默认是Thread.sleep,即线程休眠,backOffPeriod指定休眠时间,默认1秒
UniformRandomBackOffPolicy随机时间退避策略,需设置sleeper、minBackOffPeriod和maxBackOffPeriod,该策略在minBackOffPeriod,maxBackOffPeriod之间取一个随机休眠时间,minBackOffPeriod默认500毫秒,maxBackOffPeriod默认1500毫秒
ExponentialBackOffPolicy指数退避策略,需设置参数sleeper、initialInterval、maxInterval和multiplier,initialInterval指定初始休眠时间,默认100毫秒,maxInterval指定最大休眠时间,默认30秒,multiplier指定乘数,即下一次休眠时间为当前休眠时间*multiplier
ExponentialRandomBackOffPolicy随机指数退避策略,引入随机乘数可以实现随机乘数回退
  • 此外,还需要配置重试时间间隔、最大重试次数以及可重试异常

实现

代码方式

RetryTemplate通过execute提交执行操作,需要准备RetryCallback和RecoveryCallback两个类实例,前者对应的就是重试回调逻辑实例,包装正常的功能操作,RecoveryCallback实现的是整个执行操作结束的恢复操作实例,只有在调用的时候抛出了异常,并且异常是在exceptionMap中配置的异常,才会执行重试操作,否则就调用到excute方法的第二个执行方法RecoveryCallback中

package com.example.test.MessageRetry;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.remoting.RemoteAccessException;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
@Slf4j
public class SpringRetryService {
    /**
     * 重试时间间隔ms,默认1000ms
     */
    private long retryPeriodTime = 5000L;
    /**
     * 最大重试次数
     */
    private int maxRetryNum = 3;
    /**
     * 哪些结果和异常要重试,key表示异常类型,value表示是否需要重试
     */
    private Map<Class<? extends Throwable>,Boolean> retryMap = new HashMap<>();

    @Test
    public void test(){
        retryMap.put(IllegalArgumentException.class,true);
        retryMap.put(RemoteAccessException.class,true);

        //构建重试模板
        RetryTemplate retryTemplate = new RetryTemplate();
        //设置重试回退操作策略,主要设置重试时间间隔
        FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
        backOffPolicy.setBackOffPeriod(retryPeriodTime);
        //设置重试策略,主要设置重试次数
        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(maxRetryNum,retryMap);

        retryTemplate.setRetryPolicy(retryPolicy);
        retryTemplate.setBackOffPolicy(backOffPolicy);

        Boolean execute = retryTemplate.execute(
                //RetryCallback
                retryContext -> {
                    boolean b = RetryTask.retryTask("aaa");
                    log.info("调用结果:{}",b);
                    return b;
                },
                retryContext -> {
                    //RecoveryCallback
                    log.info("到达最多尝试次数");
                    return false;
                }
        );
        log.info("执行结果:{}",execute);
    }
}

在这里插入图片描述

注解方式

上面我们说到Spring-Retry是Spring提供的,那么,它就支持依赖整合

<!--spring-retry-->
        <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>

然后,在启动类上添加开启注解

//表示是否开启重试,属性proxyTargetClass,boolean类型,是否创建基于子类(CGLIB)的代理,而不是标准的基于接口的代理,默认false
@EnableRetry
package com.example.test.MessageRetry;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.remoting.RemoteAccessException;

@Slf4j
public class RetryTask {
    /**
     * 重试方法
     * @param param
     * @return
     */
    public static boolean retryTask(String param){
        log.info("请求参数:{}",param);
        int i = RandomUtils.nextInt(0,15);
        log.info("随机数:{}",i);
        if(i >-1){
            log.error("参数异常");
            throw new IllegalArgumentException("参数异常");
//        }else if(i > 10){
//            log.error("访问异常");
//            throw new RemoteAccessException("访问异常");
//        }else if(i % 2 == 0){
//            log.info("成功");
//            return true;
        }else{
            log.info("失败");
            return false;
        }
    }
}

/**
     * 重试调用方法
     * @param param
     * @return
     * @Retryable注解:
     *
     */
    @Retryable(value = {RemoteAccessException.class,IllegalArgumentException.class},maxAttempts = 3,backoff = @Backoff(delay = 5000L,multiplier = 2))
    public void call(String param){
        RetryTask.retryTask(param);
    }

    /**
     * 达到最大重试次数,或抛出了没有指定的异常
     * @param e
     * @param param
     * @return
     */
    @Recover
    public void recover(Exception e,String param){
        log.error("达到最大重试次数!!!!!");
    }
@Test
    public void retry(){
        springRetryService.call("aaa");
    }
  • @Retryable注解说明
属性说明
value指定重试的异常类型,默认为空
maxAttempts最大尝试次数,默认3次
include和value一样,默认为空,当exclude也为空时,默认所有异常
exclude指定不处理的异常
Backoff重试策略
  • @Backoff注解说明:设定重试倍数,每次重试时间是上次的n倍
属性说明
delay重试之间的等待时间(以毫秒为单位),默认0
maxDelay重试之间的最大等待时间(以毫秒为单位),默认0
multiplier延迟的倍数,默认0.0
delayExpression重试之间的等待时间表达式,默认空
maxDelayExpression重试之间的最大等待时间表达式,默认空
multiplierExpression指定延迟的倍数表达式,默认空
random随机指定延迟时间,默认false
  • @Recover注解说明:当重试到达指定次数时,将要回调的方法

  • @Retryable和@Recover修饰的方法要在同一个类中,且被@Retryable和@Recover标记的方法不能有返回值,这样Recover方法才会生效。由于@Retryable注解是通过切面实现的,因此我们要避免@Retryable 注解的方法的调用方和被调用方处于同一个类中,因为这样会使@Retryable 注解失效。
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/22e61f2d194e4ac0aa061fa4ca7d483f.png
    我们可以看到,Spring-Retry只能针对指定异常重试,不能根据执行结果返回值重试,整体使用也比较死板,下面,看下更加灵活的Guava-Retry。

Guava-Retry

简介

Guava-Retry是谷歌的Guava库的一个小扩展,允许为任意函数调用创建可配置的重试策略,我们可以通过构建重试实例RetryBuilder,来设置重试源、配置重试次数、重试超时时间、等待时间间隔等,实现优雅的重试机制。

  • 主要属性
属性说明
attemptTimeLimiter时间限制策略,单次任务执行时间限制,超时终止
stopStrategy停止重试策略
waitStrategy等待策略
blockStrategy任务阻塞策略,即当前任务执行完,下次任务执行前做什么,仅有线程阻塞threadSleepStrategy
retryException重试异常(重试策略)
listeners自定义重试监听器,可用于记录日志等
  • 时间限制策略
策略说明
NoAttemptTimeLimit对代理方法不添加时间限制,默认
FixedAttemptTimeLimit对代理方法的尝试添加固定时间限制
  • 重试策略(重试异常)
策略说明
retryIfException抛出 runtime 异常、checked 异常时都会重试,但是抛出 error 不会重试
retryIfRuntimeException只会在抛 runtime 异常的时候才重试,checked 异常和error 都不重试
retryIfExceptionOfType允许我们只在发生特定异常的时候才重试,比如NullPointerException 和 IllegalStateException 都属于 runtime 异常,也包括自定义的error
retryIfResult可以指定你的 Callable 方法在返回值的时候进行重试
  • 停止策略
策略说明
StopAfterDelayStrategy设定最长执行时间,无论任务执行几次,一旦超时,任务终止,返回RetryException
StopAfterAttemptStrategy设定最大尝试次数,一旦超过,返回重试异常
NeverStopStrategy一直轮询直到获取期望结果
  • 等待策略
策略说明
ExceptionWaitStrategy异常时长等待,如果抛出的是指定异常,则从传入的方法中取得等待时间并返回;如果异常不匹配,则返回等待时间为0L
CompositeWaitStrategy复合时长等待,在获取等待时间时会获取多种等待策略各自的等待时间,然后累加这些等待时间
FibonacciWaitStrategy斐波那契等待策略
ExponentialWaitStrategy指数等待时长,指数增长,若设置了最大时间,则停止,否则到Long.MAX_VALUE
IncrementingWaitStrategy递增等待,提供一个初始时长和步长,随次数叠加
RandomWaitStrategy随机等待时长,可以提供一个最大和最小时间,从范围内随机
FixedWaitStrategy固定等待时长

代码

<!--guava-retryer-->
        <dependency>
            <groupId>com.github.rholder</groupId>
            <artifactId>guava-retrying</artifactId>
            <version>2.0.0</version>
        </dependency>
package com.example.test.MessageRetry;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.remoting.RemoteAccessException;

@Slf4j
public class RetryTask {
    /**
     * 重试方法
     * @param param
     * @return
     */
    public static boolean retryTask(String param){
        log.info("请求参数:{}",param);
        int i = RandomUtils.nextInt(0,15);
        log.info("随机数:{}",i);
        if(i < 3){
            log.error("参数异常");
            throw new IllegalArgumentException("参数异常");
        }else if(i > 10){
            log.error("访问异常");
            throw new RemoteAccessException("访问异常");
        }else if(i % 2 == 0){
            log.info("成功");
            return true;
        }else{
            log.info("失败");
            return false;
        }
    }
}

package com.example.test.MessageRetry;

import com.github.rholder.retry.*;
import com.google.common.base.Predicates;
import lombok.extern.slf4j.Slf4j;
import org.springframework.remoting.RemoteAccessException;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class GuavaRetryService {
   
    public void guavaRetry(){
        //构建重试实例RetryBuilder,可以设置重试源,可以配置重试次数、重试超时时间、等待时间间隔
        Retryer<Boolean>retryer = RetryerBuilder.<Boolean>newBuilder()
                //设置异常重试源
                .retryIfExceptionOfType(RemoteAccessException.class)
                .retryIfExceptionOfType(IllegalArgumentException.class)
                //设置根据结果重试  res->res==false  Predicates.containsPattern("_error$")
                .retryIfResult(Predicates.equalTo(false))
                //设置等待时间
                .withWaitStrategy(WaitStrategies.fixedWait(3, TimeUnit.SECONDS))
                //设置最大重试次数
                .withStopStrategy(StopStrategies.stopAfterAttempt(3))
                //设置重试监听,可用作重试时的额外动作
                .withRetryListener(new RetryListener() {
                    @Override
                    public <V> void onRetry(Attempt<V> attempt) {
                        log.error("第【{}】次调用失败",attempt.getAttemptNumber());
                    }
                })
                //设置阻塞策略
                .withBlockStrategy(BlockStrategies.threadSleepStrategy())
                //设置时间限制
                .withAttemptTimeLimiter(AttemptTimeLimiters.noTimeLimit())
                .build();
        try{
            retryer.call(()->RetryTask.retryTask("aaa"));
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

可以看到,我们设置了重试三次,超过这个限制没有执行成功,抛出了重试异常,而且也可以根据我们的返回结果来判断。

总结

Spring-Retry和Guava-Retry都是线程安全的重试框架,能够保证并发业务下重试逻辑的正确性。两者都很好的将正常方法和重试方法进行了解耦,可以设置超时时间、重试次数、间隔时间、监听结果等,相比来说,Guava-Retry比Spring-Retry更加灵活,并且可以通过返回值来进行重试,两者都是非常好的重试框架,具体的选用看相关的业务场景即可。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

idea模板的使用(配置xml文件模板)

1. 问题的引出 我们在日常项目中可以发现&#xff0c;sql映射文件和mybatis主配置文件&#xff0c;以及application.yml文件中有很多固定不变的内容&#xff0c;为了方面使用&#xff0c;所以可以把这些xml文件设置为模板 2. 创建模板的步骤 按照图片一步一步进行即可 点击…

【Spring专题】手写简易Spring容器过程分析

前置知识 《【Spring专题】Spring底层核心原理解析》 思路整理 我们在上一节《【Spring专题】Spring底层核心原理解析》课里面有简单分析过一个Spring容器的一般流程&#xff0c;所以&#xff0c;本节课我们这里尝试写一下简易的Spring容器。 手写源码示例 一、手写前的准…

Sentinel整合Spring Cloud Gateway、Zuul详解

Sentinel 支持对 Spring Cloud Gateway、Zuul 等主流的 API Gateway 进行限流。 Sentinel 1.6.0 引入了 Sentinel API Gateway Adapter Common 模块&#xff0c;此模块中包含网关限流的规则和自定义 API 的实体和管理逻辑&#xff1a; GatewayFlowRule&#xff1a;网关限流规则…

04-8_Qt 5.9 C++开发指南_QTableWidget的使用

文章目录 1. QTableWidget概述2. 源码2.1 可视化UI设计2.2 程序框架2.3 qwintspindelegate.h2.4 qwintspindelegate.cpp2.5 mainwindow.h2.6 mainwindow.cpp 1. QTableWidget概述 QTableWidget是Qt中的表格组件类。在窗体上放置一个QTableWidget 组件后,可以在 PropertyEditor…

ELK 企业级日志分析系统(二)

目录 ELK Kiabana 部署&#xff08;在 Node1 节点上操作&#xff09; 1&#xff0e;安装 Kiabana 2&#xff0e;设置 Kibana 的主配置文件 3&#xff0e;启动 Kibana 服务 4&#xff0e;验证 Kibana 5&#xff0e;将 Apache 服务器的日志&#xff08;访问的、错误的&#x…

明年,HarmonyOS不再兼容Android应用!

2023年华为开发者大会&#xff0c;不知道各位老铁们是否观看了&#xff0c;一个震撼的消息就是&#xff0c;首次公开了HarmonyOS NEXT的概念&#xff0c;简而言之就是&#xff0c;这是一款专为开发者打造的预览版操作系统&#xff0c;旨在提供"纯正鸿蒙操作系统"的体…

throw和throws的区别

在Java中&#xff0c;throw和throws是两个关键字&#xff0c;用于异常处理。它们具有以下区别&#xff1a; 1. throw关键字&#xff1a; - throw关键字用于主动抛出异常。当程序执行到throw语句时&#xff0c;会创建一个异常对象并将其抛出。 - throw语句通常在方法内部…

把大模型装进手机,分几步?

点击关注 文 | 姚 悦 编 | 王一粟 大模型“跑”进手机&#xff0c;AI的战火已经从“云端”烧至“移动终端”。 “进入AI时代&#xff0c;华为盘古大模型将会来助力鸿蒙生态。”8月4日&#xff0c;华为常务董事、终端BG CEO、智能汽车解决方案BU CEO 余承东介绍&#xff0c…

Drools用户手册翻译——第四章 Drools规则引擎(十三)复杂事件处理(CEP)会话时钟,事件流和切入点

甩锅声明&#xff1a;本人英语一般&#xff0c;翻译只是为了做个笔记&#xff0c;所以有翻译错误的地方&#xff0c;错就错了&#xff0c;如果你想给我纠正&#xff0c;就给我留言&#xff0c;我会改过来&#xff0c;如果懒得理我&#xff0c;就直接划过即可。 目录 会话时钟…

【Paper Reading】DETR:End-to-End Object Detection with Transformers

背景 Transformer已经在NLP领域大展拳脚&#xff0c;逐步替代了LSTM/GRU等相关的Recurrent Neural Networks&#xff0c;相比于传统的RNN&#xff0c;Transformer主要具有以下几点优势 可解决长时序依赖问题&#xff0c;因为Transformer在计算attention的时候是在全局维度进行…

《论文阅读》通过生成会话模型的迁移学习会话中的情感识别

《论文阅读》通过生成会话模型的迁移学习会话中的情感识别 前言简介模型结构Source TaskTarget Task损失函数前言 你是否也对于理解论文存在困惑? 你是否也像我之前搜索论文解读,得到只是中文翻译的解读后感到失望? 小白如何从零读懂论文?和我一起来探索吧! 今天为大家…

LangChain源码逐行解密之LLMs(二)

LangChain源码逐行解密之LLMs(二) 18.3 base.py源码逐行剖析 现在我们要聚焦于源代码中的大语言模型部分。如图18-3所示,LangChain提供了许多语言模型的选择。 Gavin大咖微信:NLP_Matrix_Space 图18- 3 LangChain的llms目录 如图18-4所示,整个LangChain的模块化设计非常出…

JAVA集合框架 一:Collection(LIst,Set)和Iterator(迭代器)

目录 一、Java 集合框架体系 1.Collection接口&#xff1a;用于存储一个一个的数据&#xff0c;也称单列数据集合&#xff08;single&#xff09;。 2.Map接口&#xff1a;用于存储具有映射关系“key-value对”的集合&#xff08;couple&#xff09; 3.Iterator接口&#…

ME41询价单创建BAPI

关于ME41创建询价单系统并没有准备标准的BAPI&#xff0c;这一点在note:2115337中有说明。 但是通过查阅相关资料找到一个BAPI&#xff1a;BS01_MM_QUOTATION_CREATE&#xff0c;可以为ME41进行创建&#xff0c;但是如果不做一些增强&#xff0c;会有一些额外的错误&#xff0…

Vue 整合 Element UI 、路由嵌套、参数传递、重定向、404和路由钩子(五)

一、整合 Element UI 1.1 工程初始化 使用管理员的模式进入 cmd 的命令行模式&#xff0c;创建一个名为 hello-vue 的工程&#xff0c;命令为&#xff1a; # 1、目录切换 cd F:\idea_home\vue# 2、项目的初始化&#xff0c;记得一路的 no vue init webpack hello-vue 1.2 安装…

【代码】表格封装 + 高级查询 + 搜索 +分页器 (极简)

一、标题 查询条件按钮&#xff08;Header&#xff09; <!-- Header 标题搜索栏 --> <template><div><div class"header"><div class"h-left"><div class"title"><div class"desc-test">…

Flutter系列文章-实战项目

在本篇文章中&#xff0c;我们将通过一个实际的 Flutter 应用来综合运用最近学到的知识&#xff0c;包括保存到数据库、进行 HTTP 请求等。我们将开发一个简单的天气应用&#xff0c;可以根据用户输入的城市名获取该城市的天气信息&#xff0c;并将用户查询的城市列表保存到本地…

语音同声翻译软件到底谁更胜一筹呢

嘿&#xff01;你是否曾经遇到过需要在不同语言之间进行实时翻译的情况&#xff1f;别担心&#xff0c;现在有许多翻译软件可供选择&#xff0c;让你的沟通变得更加简便和愉快。无论你是旅行者、国际商务人士还是语言爱好者&#xff0c;这些软件都将成为你的得力助手&#xff0…

领航优配:暑期旅游市场热度持续攀升,相关公司业绩有望持续释放

到发稿&#xff0c;海看股份涨停&#xff0c;中广天择、探路者、众信旅行等涨幅居前。 8月8日&#xff0c;在线旅行板块震动上涨&#xff0c;到发稿&#xff0c;海看股份涨停&#xff0c;中广天择、探路者、众信旅行等涨幅居前。 今年以来&#xff0c;国内旅行商场逐渐恢复。文…

arcgis--数据库构建网络数据集

1、打开arcmap软件&#xff0c;导入数据&#xff0c;如下&#xff1a; 该数据已经过处理&#xff0c;各交点处均被打断&#xff0c;并进行了拓扑检查。 2、在文件夹下新建文件数据库&#xff0c;名称为路网&#xff0c;在数据库下新建要素类&#xff0c;并导入道路shp文件&…
最新文章