分享免费的编程资源和教程

网站首页 > 技术教程 正文

SpringBoot系列(21):基于Guava_Retrying机制实现重试功能

goqiw 2024-09-25 20:13:33 技术教程 82 ℃ 0 评论

对于“接口/方法 重试”,相信很多小伙伴都听说过,但是在实际项目中却很少真正去实践过,在本篇文章中,Debug将给各位小伙伴介绍一种“重试”机制的实现,即Guava_Retrying,相对于传统的Spring_Retrying或者动态代理实现的重试功能而言,本文要介绍的Guava_Retrying机制使用起来将更加容易、灵活性更强!

老赵:“这个 接口/方法 调用又失败了,老李啊,你去写个重试功能吧!”。

老李:“他娘的,这接口调用咋又不行了。。。行吧,老子立马给你撸一个重试功能” 。

这样的对话,相信有些小伙伴会感觉似曾相识!特别是当自己在工位上安安静静的写代码时,会突然性的接到技术老大分配给自己的这种需求。。。没啥好说的,只能潜下心,去研究研究了!

对于“重试”,那可是有场景限制的,不是什么场景都适合重试,比如参数校验不合法、写操作等(因为要考虑到写是否幂等)都不适合重试。

而诸如“远程调用超时”、“网络突然中断”等业务场景则可以进行重试,在微服务 治理框架中,通常都有自己的重试与超时配置,比如Dubbo可以设置retries=1,timeout=500调用失败只重试1次,超过500ms调用仍未返回则调用失败(详情可以观看学习Debug录制的“分布式服务调度Dubbo实战教程 http://www.fightjava.com/web/index/course/detail/2 ”)

对于“外部 RPC 调用”,或者“数据入库”等操作,如果一次操作失败,则可以进行多次重试,从而提高调用成功的可能性。


下面我们基于前面搭建的SpringBoot多模块企业级项目,基于Guava_Retrying初步实现所谓的“重试功能”。工欲善其事必先利其器,首先当然是需要加入Guava_Retrying的依赖Jar,如下所示:

<!--guava-retrying-->

 <dependency>

 <groupId>com.github.rholder</groupId>

 <artifactId>guava-retrying</artifactId>

 <version>2.0.0</version>

 </dependency>

之后,我们来写个简单的入门案例,先来 过一把“接口调用重试”的瘾!

/**

 * Guava_retrying机制实现重试

 * @Author:debug (SteadyJack)

 * @Link: weixin-> debug0868 qq-> 1948831260

 * @Date: 2019/12/1 16:17

 **/

public class RetryUtil {

 private static final Logger log= LoggerFactory.getLogger(RetryUtil.class);

 

 private static Integer i=1;

 

 public static Integer execute() throws Exception{

 log.info("----重试时 变量i的叠加逻辑----");

 return i++;

 }

 

 public static void main(String[] args) {

 //TODO:定义任务实例

 Callable<String> callable= () -> {

 Integer res=execute();

 //当重试达到3 + 1次之后 我们就不玩了

 if (res>3){

 return res.toString();

 }

 return null;

 };

 

 //TODO:定义重试器

 Retryer<String> retryer=RetryerBuilder.<String>newBuilder()

 //TODO:当返回结果为Null时 - 执行重试

 .retryIfResult(Predicates.isNull())

 //TODO:当执行核心业务逻辑抛出RuntimeException - 执行重试

 .retryIfRuntimeException()

 //TODO:还可以自定义抛出何种异常时 - 执行重试

 .retryIfExceptionOfType(IOException.class)

 .build();

 try {

 retryer.call(callable);

 } catch (ExecutionException | RetryException e) {

 e.printStackTrace();

 }

 }
}

运行该main方法,可以得到如下的结果:

从该上述代码中,我们得知“重试机制”功能实现的核心在于定义Retryer实例以及Callable任务运行实例 ,特别是Retryer实例,可以设置“什么时机重试”。

除此之外,对于 Retryer实例 我们还可以设置“重试的次数”、“重试的时间间隔”、“每次重试时,定义Listener监听一些操作逻辑”等等。如下代码所示:

public static void main(String[] args) {

 //TODO:定义任务实例

 Callable<String> callable= () -> {

 return null;

 };

 

 //TODO:每次重试时 监听器执行的逻辑

 RetryListener retryListener=new RetryListener() {

 @Override

 public <V> void onRetry(Attempt<V> attempt) {

 Long curr=attempt.getAttemptNumber();

 log.info("----每次重试时 监听器执行的逻辑,当前已经是第 {} 次重试了----",curr);

 

 if (curr == 3){

 log.error("--重试次数已到,是不是得该执行一些补偿逻辑,如发送短信、发送邮件...");

 }

 }

 };

 

 //TODO:定义重试器

 Retryer<String> retryer=RetryerBuilder.<String>newBuilder()

 //TODO:当返回结果为Null时 - 执行重试

 .retryIfResult(Predicates.isNull())

 //TODO:当执行核心业务逻辑抛出RuntimeException - 执行重试

 .retryIfRuntimeException()

 //TODO:还可以自定义抛出何种异常时 - 执行重试

 .retryIfExceptionOfType(IOException.class)

 

 //TODO:每次重试时的时间间隔为5s

 .withWaitStrategy(WaitStrategies.fixedWait(5L, TimeUnit.SECONDS))

 //TODO:重试次数为3次,3次之后就不重试了

 .withStopStrategy(StopStrategies.stopAfterAttempt(3))

 //TODO:每次重试时定义一个监听器listener,监听器的逻辑可以是 "日志记录"、"做一些补偿操作"...

 .withRetryListener(retryListener)

 .build();

 

 try {

 retryer.call(callable);

 } catch (ExecutionException | RetryException e) {

 e.printStackTrace();

 }

 }

其中,我们加入了“监听器Listener”、“定义了重试次数”、“定义了每次重试的时间间隔”,这三个才是Guava_Retrying提供给开发者重量级的玩意,如下代码所示!

//TODO:每次重试时的时间间隔为5s

 .withWaitStrategy(WaitStrategies.fixedWait(5L, TimeUnit.SECONDS))

 //TODO:重试次数为3次,3次之后就不重试了

 .withStopStrategy(StopStrategies.stopAfterAttempt(3))

 //TODO:每次重试时定义一个监听器listener,监听器的逻辑可以是 "日志记录"、"做一些补偿操作"...

 .withRetryListener(retryListener)

其中,“重试次数策略StopStrategies”、“重试的时间间隔设置策略WaitStrategies”中Guava_Retrying提供了许多种选择,比如“重试次数可以是一个随机数”、“重试的时间间隔也可以设置为某个区间范围内的随机数”等等,下图为运行结果截图:

下面,我们来撸一个真实的业务场景,即“调用某个接口的方法,用于获取SysConfig配置表中某个字典配置记录,如果该字典配置记录不存在(即返回Null),那我们就重试3次,如果期间获取到了,那么就返回结果;3次过后,依旧为Null时,则执行一些补偿性的措施:即发送邮件通知给到指定的人员,让他们上去检查检查相应的数据状况!”

下图为 系统字典配置表SysConfig存储的字典记录,其中,没有id=11的记录,我们将拿着这个 id=11 来进行测试:

如下代码为正常项目开发过程中我们自定义的Service及其方法:

/**

 * Guava_Retrying重试机制的 小型真实案例

 * @Author:debug (SteadyJack)

 * @Link: weixin-> debug0868 qq-> 1948831260

 * @Date: 2019/12/1 17:51

 **/

@Service

public class RetryService {

 private static final Logger log= LoggerFactory.getLogger(RetryService.class);

 

 @Autowired

 private SysConfigMapper sysConfigMapper;

 

 @Autowired

 private EmailSendService emailSendService;

 

 //TODO:获取某个字典配置详情

 public SysConfig getConfigInfo(final Integer id){

 SysConfig config=sysConfigMapper.selectByPrimaryKey(id);

 

 if (config==null){

 //TODO:当没有查询到该数据记录时,执行重试逻辑

 doRetry(id);

 config=sysConfigMapper.selectByPrimaryKey(id);

 }

 return config;

 }

 

 

 //TODO:执行重试逻辑

 private void doRetry(final Integer id){

 

 //TODO:定义任务实例

 Callable<SysConfig> callable= () -> {

 return sysConfigMapper.selectByPrimaryKey(id);

 };

 

 //TODO:每次重试时 监听器执行的逻辑

 RetryListener retryListener=new RetryListener() {

 @Override

 public <V> void onRetry(Attempt<V> attempt) {

 Long curr=attempt.getAttemptNumber();

 log.info("----每次重试时 监听器执行的逻辑,当前已经是第 {} 次重试了----",curr);

 

 //当达到3次时 就执行一些补偿性的措施,如发送邮件通知某些大佬….

 if (curr == 3){

 log.error("--重试次数已到,是不是得该执行一些补偿逻辑,如发送短信、发送邮件...");

 

 emailSendService.sendSimpleEmail("重试次数已到","请各位大佬上去检查一下sysConfig是否存在","1948831260@qq.com");

 }

 }

 };

 

 //TODO:定义重试器

 Retryer<SysConfig> retryer= RetryerBuilder.<SysConfig>newBuilder()

 //TODO:当返回结果为 false 时 - 执行重试(即sysCofig为null)

 .retryIfResult(Objects::isNull)

 //TODO:当执行核心业务逻辑抛出RuntimeException - 执行重试

 .retryIfRuntimeException()

 //TODO:还可以自定义抛出何种异常时 - 执行重试

 .retryIfExceptionOfType(IOException.class)

 

 //TODO:每次重试时的时间间隔为10s (当然啦,实际项目中一般是不超过1s的,如500ms,这里是为了方便模拟演示)

 .withWaitStrategy(WaitStrategies.fixedWait(10L, TimeUnit.SECONDS))

 //TODO:重试次数为3次,3次之后就不重试了

 .withStopStrategy(StopStrategies.stopAfterAttempt(3))

 //TODO:每次重试时定义一个监听器listener,监听器的逻辑可以是 "日志记录"、"做一些补偿操作"...

 .withRetryListener(retryListener)

 

 .build();

 try {

 retryer.call(callable);

 } catch (ExecutionException | RetryException e) {

 e.printStackTrace();

 }

 }

}

最后写个Java Unit Test,即Java单元测试案例,如下所示:

@Autowired

 private RetryService retryService;

 

 @Test

 public void method8() throws Exception{

 final Integer id=11;

 

 SysConfig entity=retryService.getConfigInfo(id);

 log.info("---结果:{}",entity);

 }

点击运行该单元测试案例,啥事都不要做,等待运行结果,你会发现“重试”的效果我们已经实现了!如下所示:

我们再点击运行该单元测试案例,然后在它运行了第1次重试机会之后,我们赶紧手动到数据库将 id=12 的那条系统配置记录,调整为 id=11 !然后再来看运行的结果,如下图所示:

如下图为“补偿性措施”中的“发送邮件”:

好了,本篇文章我们就介绍到这里了,建议各位小伙伴一定要照着文章提供的样例代码撸一撸,只有撸过才能知道这玩意是咋用的,否则就成了“空谈者”。其他相关的技术,感兴趣的小伙伴可以关注Debug的技术公众号,或者私信Debug!

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表