zoukankan      html  css  js  c++  java
  • Guava Retryer实现接口重试

    前言

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

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

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

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

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

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

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

    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 风格的重试方式真的很方便。

    引入依赖

    <dependency>
          <groupId>com.github.rholder</groupId>
          <artifactId>guava-retrying</artifactId>
          <version>2.0.0</version>
    </dependency>
    

    快速开始

    Callable<Boolean> callable = new Callable<Boolean>() {
        public Boolean call() throws Exception {
            return true; // do something useful here
        }
    };
    
    Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
        .retryIfResult(Predicates.<Boolean>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<Boolean> retryer = RetryerBuilder.<Boolean>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<Boolean> retryer = RetryerBuilder.<Boolean>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<Boolean> implements RetryListener {
        @Override
        public <Boolean> void onRetry(Attempt<Boolean> attempt) {
            log.info("重试次数:{}",attempt.getAttemptNumber());
            log.info("距离第一次重试的延迟:{}",attempt.getDelaySinceFirstAttempt());
            if(attempt.hasException()){
                log.error("异常原因:",attempt.getExceptionCause());
            }else {
                System.out.println("正常处理结果:{}" + attempt.getResult());
            }
        }
    }
    

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

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

    小结

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

  • 相关阅读:
    CCF——分蛋糕(2017-3)
    CCF——公共钥匙盒(2017-9)
    CCF——打酱油(2017-9)
    CCF——游戏(2017-12)
    SDS-简单动态字符串
    Redis主从复制
    MySQL 知识点
    MySQL 死锁
    Java 类加载机制
    Java IO
  • 原文地址:https://www.cnblogs.com/heiz123/p/15733274.html
Copyright © 2011-2022 走看看