热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

如何在业务逻辑中引入重试机制:springRetry和GuavaRetry

前言为什么要引入重试机制?我们首先看看正常的业务系统交互流程,就像下面图中所示一样,我们自己开发的系统通过HTTP接口或者通过RPC去访

前言

为什么要引入重试机制?

        我们首先看看正常的业务系统交互流程,就像下面图中所示一样,我们自己开发的系统通过HTTP接口或者通过RPC去访问其他业务系统,其他系统在没出现任何问题的情况下会返回给我们需要的数据,状态为success。

        但大家在日常的开发工作当中应该碰到过不少这样的问题:自己应用因为业务需求需要调其他关联应用的接口或二方包,而其他应用的接口稳定性不敢过分恭维,老是出一些莫名奇妙的幺蛾子,比如,由于接口暂时升级维护导致的短暂不可用,又或者网络抖动因素导致的单次接口请求失败。

        诸如此类的麻烦问题会因为业务强依赖致使我们自己维护的系统也跟着陷入一种不稳定的状态(当然这个强依赖是没有办法的事情,毕竟业务之间需要解耦独立开发维护)。

        所以,也就是说重试的使用场景大多是因为我们的系统依赖了其他的业务,或者是由于我们自己的业务需要通过网络请求去获取数据这样的场景。既然一次请求结果的状态非常不可控、不稳定,那么一个非常自然的想法就是多试几次,就能很好的避开网络抖动或其他关联应用暂时down机维护带来的系统不可用问题。


答疑解惑

当然,这里也有几个引入重试机制以后需要考虑的问题需要我们先解决一下:

  1. 我们应该重试几次?
  2. 每次重试的间隔设置为多少合适?
  3. 如果所有重试机会都用完了还是不成功怎么办?

问题1:重试几次合适?

        通常来说我们单次重试所面临的情况就如上面我们分析的一样,有很大的不可确定性,那到底多少次是比较合理的次数呢?这个就要“具体业务具体分析”了,但一般来说3次重试就差不多可以满足大多数业务需求了,当然,这是需要结合后面要说的重试间隔一起讨论的。

        为什么说3次就基本够了呢,因为如果被请求系统实在处于长时间不可用状态。我们重试多次是没有什么意义的。重试的同时还要注意请求时间的问题,如果存在超时风险,那么对第三方的请求方法就一概是异步进行的。

问题2:重试间隔设置为多少合适?

        如果重试间隔设置得太小,可能被调用系统还没来得及恢复过来我们就又发起调用,得到的结果肯定还是Fail;

        如果设置的太大,我们自己的系统就会牺牲掉不少数据时效性。所以,重试间隔也要根据被调用的系统平均恢复时间去正确估量,通常而言这个平均恢复时间很难统计到,所以一般的经验值是3至5分钟。

问题3:重试机会用完以后依旧Fail怎么办?

        这种情况也是需要认真考虑的,因为不排除被调用系统真的起不来的情况,这时候就需要采取一定的补偿措施了。

  • 首先,要做的就是在我们自己的系统增加错误报警机制,这样我们才能即时感知到应用发生了不可自恢复的调用异常。
  • 其次,就是在我们的代码逻辑中加入触发手动重试的开关,这样在发生异常情况以后我们就可以方便的修改触发开关然后手动重试。

        在这里还有一个非常重要的问题需要考虑,那就是接口调用的幂等性问题,如果接口不是幂等的,那我们手动重试的时候就很容易发生数据错乱相关的问题。


Spring-Retry 和 Guava-Retry

        Spring-Retry 和 Guava-Retry 工具都可以再项目中为我们引入重试机制,而且都是线程安全的重试,先简具体介绍下使用方法:

1. Spring-Retry 用法

        Spring 为我们提供了原生的重试类库,我们可以方便地引入到工程当中,利用它提供的重试注解,没有太多的业务逻辑侵入性。

        但是,Spring 的重试机制也存在一定的不足,只支持对异常进行捕获,而无法对返回值进行校验。在实际开发中,必须将重试的场景封装成自定义异常以后才可以捕获,而这种靠异常来控制业务流程的做法是开发大忌,导致spring-Retry 的适用场景就收到了很大限制。

        所以,这种方法我只简单分享一下,更详细的用法和属性大家参阅《Spring Retry的文档》就好了,解释的非常清楚。推荐一片文章:《重试框架Guava-Retry和spring-Retry用法介绍》

  • 首先,pom.xml引入相关依赖包;

org.springframework.retryspring-retry1.2.2.RELEASE
org.aspectjaspectjweaver1.9.1

  •  然后,在启动类或者配置类上添加@EnableRetry注解;

@SpringBootApplication
@EnableRetry
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}

  • 最后,在需要重试的方法上添加@Retryable注解或@Recover注解。这两个注解功能不同,下面由具体说明;

// 需要重试所调用的方法
@Retryable
public String call(){long times = helloTimes.incrementAndGet();log.info("hello times:{}", times);if (times % 4 != 0){log.warn("发生异常,time:{}", LocalTime.now() );throw new HelloRetryException("发生Hello异常");}return "hello";
}// 达到最大重试次数,或抛出了一个没有指定进行重试的异常所调用的方法
@Recover
public String recover(){log.info("重试失败的业务逻辑。");return "fail";
}

2. Guava-Retry 用法

        相比 Spring Retry,Guava Retry具有更强的灵活性,不仅支持多个异常的重试源定义,还可以根据返回值校验来判断是否需要进行重试。

        用的时候也很简单,先创建一个Retryer实例,然后使用这个实例对需要重试的方法进行调用,可以通过很多方法来设置重试机制,比如:

  1. 使用 retryIfException 来对所有异常进行重试;
  2. 使用 retryIfExceptionOfType 方法来设置对指定异常进行重试;
  3. 使用 retryIfResult 来对不符合预期的返回结果进行重试;
  4. 使用 retryIfRuntimeException 方法来对所有 RuntimeException 进行重试。
  • 首先,pom.xml引入相关依赖包;

com.github.rholderguava-retrying2.0.0

  • 定义 Retryer 实例

@Slf4j
@Service
public class RetryDemoTask {public static Retryer retryer;// RetryerBuilder 构建重试实例 retryer,// 可以设置重试源且可以支持多个重试源&#xff0c;可以配置重试次数或重试超时时间&#xff0c;以及可以配置等待时间间隔static {retryer &#61; RetryerBuilder. newBuilder().retryIfExceptionOfType(RemoteAccessException.class) // 设置异常重试源 .retryIfResult(res-> res&#61;&#61;false) // 设置根据结果重试 .withWaitStrategy(WaitStrategies.fixedWait(3, TimeUnit.SECONDS)) // 设置等待间隔时间.withStopStrategy(StopStrategies.stopAfterAttempt(3)) // 设置最大重试次数.build();}// 测试的重试方法public static boolean randomTask(String param) {int i &#61; RandomUtils.nextInt(0,10);if (i <3) {log.info("随机生成的数&#xff1a;{}&#xff0c;小于3&#xff0c;模拟校验失败&#xff0c;不需要重试。", i);throw new IllegalArgumentException("参数异常");} else if (i <5) {log.info("随机生成的数&#xff1a;{}&#xff0c;小于5&#xff0c;模拟网络抖动&#xff0c;需要重试。", i);throw new RemoteAccessException("自定义异常");} else if (i <7) {log.info("随机生成的数&#xff1a;{}&#xff0c;小于7&#xff0c;模拟请求失败&#xff0c;返回 false", i);return false;}log.info("随机生成的数&#xff1a;{}&#xff0c;大于7&#xff0c;模拟请求成功&#xff0c;返回 true", i);return true;}
}

  • Junit 测试一下

&#64;Slf4j
public class RetrryTest {&#64;Resourceprivate SpringRetryDemo springRetryDemo;&#64;Testvoid spring_retry_test() {log.info("---------------- 测试开始 ---------------");try {RetryDemoTask.retryer.call(() -> RetryDemoTask.randomTask("测试 gunva-retry 重试机制"));} catch (IllegalArgumentException e) {log.error(e.getMessage());} catch (Exception e) {log.error(e.getMessage());}log.info("---------------- 测试结束 ---------------");}
}

  • 测试结果分析&#xff1a;

        1. 返回成功&#xff0c;不需要重试&#xff1b;

        2. 返回失败或者定义重试的异常&#xff0c;需要重试&#xff1b;

        3. 遇到未定义的异常&#xff0c;直接返回&#xff0c;不需要重试&#xff1b;

        4. 超过设定的重试次数&#xff0c;不再重试&#xff1b;


总结

        Spring-Retry 和 Guava-Retry 工具都是线程安全的重试&#xff0c;能够支持并发业务场景的重试逻辑正确性。两者都很好的将正常方法和重试方法进行了解耦&#xff0c;可以设置超时时间&#xff0c;重试次数&#xff0c;间隔时间&#xff0c;监听结果&#xff0c;都是不错的框架&#xff0c;但是&#xff0c;明显感觉得到 Guava-Retry 在使用上更便捷&#xff0c;更灵活。

        相比Spring&#xff0c;Guava Retry提供了几个核心特性&#xff1a;

  • 可以设置任务单次执行的时间限制&#xff0c;如果超时则抛出异常。
  • 可以设置重试监听器&#xff0c;用来执行额外的处理工作。
  • 可以设置任务阻塞策略&#xff0c;即可以设置当前重试完成&#xff0c;下次重试开始前的这段时间做什么事情。
  • 可以通过停止重试策略和等待策略结合使用来设置更加灵活的策略&#xff0c;比如指数等待时长并最多10次调用&#xff0c;随机等待时长并永不停止等等。

        上面针对我们引入重试机制需要思考的几个核心问题&#xff0c;以及为重试机制提供良好支持的工具类库都分别作了简单介绍&#xff0c;相信大家在今后的开发工作中遇到类似场景也能驾轻就熟地使用思考了。

        我们日常工作中有很多“大”的业务场景需要我们集中精力去突破、去思考&#xff0c;但也有很多类似的“小”点需要我们去打穿、吃透&#xff0c;大家共勉。




推荐阅读
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • springboot项目引入jquery浏览器报404错误的解决办法
    本文介绍了在springboot项目中引入jquery时,可能会出现浏览器报404错误的问题,并提供了解决办法。问题可能是由于将jquery.js文件复制粘贴到错误的目录导致的,解决办法是将文件复制粘贴到正确的目录下。如果问题仍然存在,可能是其他原因导致的。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 图像因存在错误而无法显示 ... [详细]
  • 开发笔记:spring boot项目打成war包部署到服务器的步骤与注意事项
    本文介绍了将spring boot项目打成war包并部署到服务器的步骤与注意事项。通过本文的学习,读者可以了解到如何将spring boot项目打包成war包,并成功地部署到服务器上。 ... [详细]
  • 微信官方授权及获取OpenId的方法,服务器通过SpringBoot实现
    主要步骤:前端获取到code(wx.login),传入服务器服务器通过参数AppID和AppSecret访问官方接口,获取到OpenId ... [详细]
  • 花瓣|目标值_Compose 动画边学边做夏日彩虹
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Compose动画边学边做-夏日彩虹相关的知识,希望对你有一定的参考价值。引言Comp ... [详细]
author-avatar
捕鱼达人2602884285
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有