前言

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

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

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

问题来了,如果又失败了呢?接着重试嘛。我们循环处理,比如循环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确实强大。

Guava Retryer实现接口重试的更多相关文章

  1. 使用Guava retryer优雅的实现接口重调机制

    API 接口调用异常, 网络异常在我们日常开发中经常会遇到,这种情况下我们需要先重试几次调用才能将其标识为错误并在确认错误之后发送异常提醒.guava-retry可以灵活的实现这一功能.Guava r ...

  2. 使用Guava retryer优雅的实现接口重试机制

    转载自: 使用Guava retrying优雅的实现接口重调机制 Guava retrying:基于 guava 的重试组件 实际项目中,为了考虑网络抖动,加锁并发冲突等场景,我们经常需要对异常操作进 ...

  3. 异步回调实现- Guava Retryer

    为什么要使用重试利器Retryer 在实际开发中我们经常会遇到需要轮询查询一个接果,实现轮询的方式有很多种,我们经常要写许多代码,有时还会怕写出的代码有bug,如果已经有轮子了,我们就没必要重复造轮子 ...

  4. Guava RateLimiter实现接口API限流

    一.简介 Guava提供的RateLimiter可以限制物理或逻辑资源的被访问速率.RateLimit二的原理类似与令牌桶,它主要由许可发出的速率来定义,如果没有额外的配置,许可证将按每秒许可证规定的 ...

  5. Google Guava缓存实现接口的限流

    一.项目背景 最近项目中需要进行接口保护,防止高并发的情况把系统搞崩,因此需要对一个查询接口进行限流,主要的目的就是限制单位时间内请求此查询的次数,例如1000次,来保护接口. 参考了 开涛的博客聊聊 ...

  6. 【Guava】Optional接口来避免空指针错误

    null会带来很多问题,从开始有null开始有无数程序栽在null的手里,null的含义是不清晰的,检查null在大多数情况下是不得不做的,而我们又在很多时候忘记了对null做检查,在我们的产品真正投 ...

  7. RateLimit--使用guava来做接口限流

    转:https://blog.csdn.net/jiesa/article/details/50412027 一.问题描述   某天A君突然发现自己的接口请求量突然涨到之前的10倍,没多久该接口几乎不 ...

  8. java反射实现接口重试

    工具类: import java.lang.reflect.Method; public class RetryUtil { private static ThreadLocal<Integer ...

  9. Guava 常用工具类

    引入guava包: <dependency> <groupId>com.google.guava</groupId> <artifactId>guava ...

随机推荐

  1. Codeforces 1392I - Kevin and Grid(平面图的欧拉定理+FFT)

    Codeforces 题面传送门 & 洛谷题面传送门 模拟赛考到一道和这题有点类似的题就来补了 神仙 GLBR I %%%%%%%%%%%%%%%%%%%% 不过感觉见过类似的题目之后就比较套 ...

  2. Codeforces 1528F - AmShZ Farm(转化+NTT+推式子+第二类斯特林数)

    Codeforces 题目传送门 & 洛谷题目传送门 神仙题,只不过感觉有点强行二合一(?). 首先考虑什么样的数组 \(a\) 符合条件,我们考虑一个贪心的思想,我们从前到后遍历,对于每一个 ...

  3. Atcoder Grand Contest 031 D - A Sequence of Permutations(置换+猜结论)

    Atcoder 题面传送门 & 洛谷题面传送门 猜结论神题. 首先考虑探究题目中 \(f\) 函数的性质,\(f(p,q)_{p_i}=q_i\leftarrow f(p,q)\circ p= ...

  4. MongoDB 安装/启动/基本操作命令

    1.MongoDB简介 MongoDB是一个基于分布式文件存储的数据库,由C++语言编写,旨在为WEB应用提供可扩展的高性能数据存储解决方案. MongoDB是一个介于关系型数据库和非关系数据库之间的 ...

  5. python16线程

    python对于I/O密集型应用比较好,具体根据是什么类型应用来查看 对于cpu密集型应用可以借助python的一些扩展去实现 thread模块是比较早期的模块,thresding是比较新的模块,对t ...

  6. 关于基因GO分析的DAVID简单使用

    利用DAVID简单的进行GO富集度分析(这里只做简单的分析,即看基因是否存在在GO的三个过程里面) 比如我们有一组要分析的基因:TRPV6    CXADR    PROM1    GRAMD2   ...

  7. mysql-计算排名

    mysql计算排名,获取行号rowno 学生成绩表数据 SELECT * FROM table_score ORDER BY score DESC; 获取某个学生成绩排名并计算该学生和上一名学生成绩差 ...

  8. 联盛德 HLK-W806 (六): I2C驱动SSD1306 128x64 OLED液晶屏

    目录 联盛德 HLK-W806 (一): Ubuntu20.04下的开发环境配置, 编译和烧录说明 联盛德 HLK-W806 (二): Win10下的开发环境配置, 编译和烧录说明 联盛德 HLK-W ...

  9. C++中Try Catch中的继承

    1.C++中Try Catch简介:我们编译运行程序出错的时候,编译器就会抛出异常.抛出异常要比终止程序灵活许多. 而C++异常是指在程序运行时发生的反常行为,这些行为超出了函数正常功能的范围.当程序 ...

  10. 紧张 + 刺激,源自一次 OOM 历险

    作者 | 蚂蝗 背景 ​ Erda 是集 DevOps.微服务治理.多云管理以及快数据管理等多功能的开源一站式企业数字化平台.其中,在 DevOps 模块中,不仅有 CI/CD.项目协同等功能,同时还 ...