Guava Retryer实现接口重试

前言

小黑在开发中遇到个问题,我负责的模块需要调用某个三方服务接口查询信息,查询结果直接影响后续业务逻辑的处理;

这个接口偶尔会因网络问题出现超时,导致我的业务逻辑无法继续处理;

这个问题该如何解决呢?,小黑首先想到的就是重试嘛,如果失败了就再调用一次。

问题来了,如果又失败了呢?接着重试嘛。我们循环处理,比如循环5次,全失败则任务服务不可用,结束调用。

如果我又想着5次调用间隔一段时间呢?第一次先隔1秒,然后3秒,然后5秒呢?

小黑发现事情没那么简单,如果自己搞容易出BUG呀。

Guava Retryer实现接口重试

转念一想,这个常见挺常见,网上应该有轮子呀,找找看。一不小心就让我给找着啦,哈哈。

Guava Retryer

This is a small extension to Google’s Guava library to allow for the creation of configurable retrying strategies for an arbitrary function call, such as something that talks to a remote service with flaky uptime.

使用Guava Retryer你可以自定义来执行重试,同时也可以监控每次重试的结果和行为,最重要的基于 Guava 风格的重试方式真的很方便。

引入依赖


      com.github.rholder
      guava-retrying
      2.0.0

快速开始

Callable callable = new Callable() {
    public Boolean call() throws Exception {
        return true; // do something useful here
    }
};

Retryer retryer = RetryerBuilder.newBuilder()
    .retryIfResult(Predicates.isNull()) // callable返回null时重试
    .retryIfExceptionOfType(IOException.class) // callable抛出IOException重试
    .retryIfRuntimeException() // callable抛出RuntimeException重试
    .withStopStrategy(StopStrategies.stopAfterAttempt(3)) // 重试3次后停止
    .build();
try {
    retryer.call(callable);
} catch (RetryException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
}

Callablecall()方法返回null,抛出 IOException或者 RuntimeException时会重试;
将在尝试重试3次后停止,并抛出包含上次失败尝试信息的 RetryException;
如果call()方法中弹出任何其他异常,它将被包装并在 ExecutionException中重新调用。

指数退避(Exponential Backoff)

根据wiki上对 Exponential backoff的说明,指数补偿是一种通过反馈,成倍地降低某个过程的速率,以逐渐找到合适速率的算法。

在以太网中,该算法通常用于冲突后的调度重传。根据时隙和重传尝试次数来决定延迟重传。

c次碰撞后(比如请求失败),会选择0和 2^c - 1之间的随机值作为时隙的数量。

对于第1次碰撞来说,每个发送者将会等待0或1个时隙进行发送。
而在第2次碰撞后,发送者将会等待0到3( 由 2^2 -1 计算得到)个时隙进行发送。
而在第3次碰撞后,发送者将会等待0到7( 由 2^3 - 1 计算得到)个时隙进行发送。
以此类推……

Retryer retryer = RetryerBuilder.newBuilder()
        .retryIfExceptionOfType(IOException.class)
        .retryIfRuntimeException()
        .withWaitStrategy(WaitStrategies.exponentialWait(100, 5, TimeUnit.MINUTES)) // 指数退避
        .withStopStrategy(StopStrategies.neverStop()) // 永远不停止重试
        .build();

创建一个永远重试的重试器,在每次重试失败后以指数级退避间隔递增,直到最多5分钟。5分钟后,从那时起每隔5分钟重试一次。

斐波那契退避(Fibonacci Backoff)

斐波那契数列指的是这样一个数列:

0,1,1,2,3,5,8,13,21,34,55,89…

这个数列从第3项开始,每一项都等于前两项之和。

Retryer retryer = RetryerBuilder.newBuilder()
        .retryIfExceptionOfType(IOException.class)
        .retryIfRuntimeException()
        .withWaitStrategy(WaitStrategies.fibonacciWait(100, 2, TimeUnit.MINUTES)) // 斐波那契退避
        .withStopStrategy(StopStrategies.neverStop())
        .build();

创建一个永远重试的重试器,在每次重试失败后以增加斐波那契退避间隔的方式等待,直到最多2分钟。2分钟后,从那时起每隔2分钟重试一次。

与指数退避策略类似,斐波那契退避策略遵循一种模式,即在每次尝试失败后等待的时间越来越长。

对于这两种策略的性能英国利兹大学专门做过性能测试,相比指数退避策略,斐波那契退避策略可能性能更好,吞吐量可能也更好。

重试监听器

当重试发生时,如果需要额外做一些动作,比如发送邮件通知之类的,可以通过 RetryListener,Guava Retryer在每次重试之后会自动回调监听器,并且支持注册多个监听。

@Slf4j
class DiyRetryListener implements RetryListener {
    @Override
    public  void onRetry(Attempt attempt) {
        log.info("重试次数:{}",attempt.getAttemptNumber());
        log.info("距离第一次重试的延迟:{}",attempt.getDelaySinceFirstAttempt());
        if(attempt.hasException()){
            log.error("异常原因:",attempt.getExceptionCause());
        }else {
            System.out.println("正常处理结果:{}" + attempt.getResult());
        }
    }
}

定义监听器之后,需要在Retryer中进行注册。

        Retryer retryer = RetryerBuilder.newBuilder()
                .retryIfResult(Predicates.isNull()) // callable返回null时重试
                .retryIfExceptionOfType(IOException.class) // callable抛出IOException重试
                .retryIfRuntimeException() // callable抛出RuntimeException重试
                .withStopStrategy(StopStrategies.stopAfterAttempt(3)) // 重试3次后停止
                .withRetryListener(new DiyRetryListener()) // 注册监听器
                .build();

小结

Guava Retryer不光在重试策略上支持多种选择,并且将业务逻辑的处理放在 Callable中,和重试处理逻辑分开,实现了解耦,这比小黑自己去写循环处理要优秀太多啦,Guava确实强大。

Guava Retryer实现接口重试

Original: https://www.cnblogs.com/heiz123/p/15733274.html
Author: 小黑说Java
Title: Guava Retryer实现接口重试

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/580192/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • RabbitMQ Win10安装

    RabbitMQ是消息对列,主要是用于做消息代理。本质上说,它接受来自生产者的信息,并将它们传递给消费者。在两者之间, 它可以根据你给它的路由,缓冲规则有选择地进行传递消息。采用E…

    Java 2023年5月30日
    067
  • 【Java全栈进阶】-继承

    抽象类的案例代码: //研发部员工 abstract class Developer{ public abstract void work(); //抽象函数,需要abstract…

    Java 2023年6月7日
    071
  • Linux下搭建maven(maven3.6+nexus3.2)私服

    准备maven和nexus安装包,nexus安装包好像要FQ,不然下载不到! 链接:https://pan.baidu.com/s/1bVMadGoTAK9pSLW6yBNOCg提…

    Java 2023年6月8日
    091
  • Java-个人杂记

    on duplicate key update MYSQL特有语法,当INSERT已经存在的记录时,执行UPDATE INSERT INTO TABLE_NAME (column1…

    Java 2023年6月8日
    092
  • 网络七层协议地图,报文格式一览无遗。绝对是干货,值得收藏

    从事网络工作的小伙伴们,在排查网络故障时,往往需要抓包,分析问题。所以,要对每一层的报文格式有一定的了解才行。但是,每一层报文协议众多,全部背下来是不可能的。只需要用到的再去查。 …

    Java 2023年6月8日
    0166
  • Invalid bound statement (not found)

    SpringBoot 配置多数据源 无非就是namespace写错了、mapper接口或者xml文件名字没对应上等 mapper.xml中的namespace和实际的mapper文…

    Java 2023年6月13日
    063
  • Java 多线程

    线程创建, Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。Java可以用四种方式来创建线程,如下所示: 1)继承Thread类创建线程 2…

    Java 2023年6月9日
    087
  • Markdown基本操作

    Markdowm学习 1)添加标题 一 ~ 六级标题的添加: 一级:# + 标题 二级:## + 标题 ……………&#8…

    Java 2023年6月15日
    075
  • 这个世界上只有一种真正的英雄主义,那就是认清生活的真相并且仍然热爱它

    人生当中成功只是一时的,失败却是主旋律。但是如何面对失败,却把人分成了不同的样子。有的人会被失败击垮,有的人能够不断地爬起来继续向前。澳大利亚队是如此的,西班牙队也是如此。我想真正…

    Java 2023年6月8日
    056
  • RabbitMQ—–死信队列

    1.什么是TTL? a. time to live 消息存活时间 b. 如果消息在存活时间内未被消费,则会被清除 c. RabbitMQ支持两种ttl设置 -单独消息进行配置ttl…

    Java 2023年5月30日
    090
  • 『硬核』Spring完整版学习笔记,动力节点王鹤

    Spring学习笔记(来自王鹤老师讲的最新版Spring) spring笔记官方下载地址:动力节点官网 视频观看地址 https://www.bilibili.com/video/…

    Java 2023年6月9日
    053
  • 【数据结构】了解KMP算法和部分匹配值、以及next函数值

    最近生活发生了很多变化,没变的是自己还是咸鱼一条,害~~ 1、什么是 KMP 算法 KMP算法是一种改进的字符串匹配算法。 2、KMP算法的思想 KMP算法的关键是利用匹配失败后的…

    Java 2023年6月5日
    098
  • 设计模式 11 外观模式

    外观模式(Facade Pattern)属于 结构型模式 在生活中,经常遇到这样的情况:办理一个业务,需要找很多部门签字盖章,这些部门往往距离较远,无奈只得四处奔波。这时候相信所有…

    Java 2023年6月6日
    097
  • JMeter 测试 ActiveMq

    JMeter 测试 ActiveMq 的资料非常少, 我花了大量的时间才研究出来 关于ActiveMq 的文章请参考我另外的文章。 版本号: ActiveMq 版本号: 5.91 …

    Java 2023年5月29日
    067
  • Spring(三):IoC的本质

    一、图例 对照上面的图,我们回想上一篇中几个代码的实现,在没有set注入之前,代码运行完全由Service层控制,用户没有选择权,选择权在程序员手中;但是使用set注入之后,用户可…

    Java 2023年6月15日
    085
  • SpringBoot启动流程分析原理(一)

    我们都知道 SpringBoot自问世以来,一直有一个响亮的口号”约定优于配置”,其实一种按约定编程的软件设计范式,目的在于减少软件开发人员在工作中的各种繁…

    Java 2023年6月5日
    083
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球