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();
}
在Callable的call()方法返回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实现接口重试的更多相关文章
- 使用Guava retryer优雅的实现接口重调机制
API 接口调用异常, 网络异常在我们日常开发中经常会遇到,这种情况下我们需要先重试几次调用才能将其标识为错误并在确认错误之后发送异常提醒.guava-retry可以灵活的实现这一功能.Guava r ...
- 使用Guava retryer优雅的实现接口重试机制
转载自: 使用Guava retrying优雅的实现接口重调机制 Guava retrying:基于 guava 的重试组件 实际项目中,为了考虑网络抖动,加锁并发冲突等场景,我们经常需要对异常操作进 ...
- 异步回调实现- Guava Retryer
为什么要使用重试利器Retryer 在实际开发中我们经常会遇到需要轮询查询一个接果,实现轮询的方式有很多种,我们经常要写许多代码,有时还会怕写出的代码有bug,如果已经有轮子了,我们就没必要重复造轮子 ...
- Guava RateLimiter实现接口API限流
一.简介 Guava提供的RateLimiter可以限制物理或逻辑资源的被访问速率.RateLimit二的原理类似与令牌桶,它主要由许可发出的速率来定义,如果没有额外的配置,许可证将按每秒许可证规定的 ...
- Google Guava缓存实现接口的限流
一.项目背景 最近项目中需要进行接口保护,防止高并发的情况把系统搞崩,因此需要对一个查询接口进行限流,主要的目的就是限制单位时间内请求此查询的次数,例如1000次,来保护接口. 参考了 开涛的博客聊聊 ...
- 【Guava】Optional接口来避免空指针错误
null会带来很多问题,从开始有null开始有无数程序栽在null的手里,null的含义是不清晰的,检查null在大多数情况下是不得不做的,而我们又在很多时候忘记了对null做检查,在我们的产品真正投 ...
- RateLimit--使用guava来做接口限流
转:https://blog.csdn.net/jiesa/article/details/50412027 一.问题描述 某天A君突然发现自己的接口请求量突然涨到之前的10倍,没多久该接口几乎不 ...
- java反射实现接口重试
工具类: import java.lang.reflect.Method; public class RetryUtil { private static ThreadLocal<Integer ...
- Guava 常用工具类
引入guava包: <dependency> <groupId>com.google.guava</groupId> <artifactId>guava ...
随机推荐
- Codeforces 1290D - Coffee Varieties(分块暴力+完全图的链覆盖)
Easy version:Codeforces 题面传送门 & 洛谷题面传送门 Hard version:Codeforces 题面传送门 & 洛谷题面传送门 发现自己交互题烂得跟 s ...
- PowerDotNet平台化软件架构设计与实现系列(05):ETCD分布式键值存储平台
ETCD目前在PowerDotNet已经被用于注册中心和配置管理(常见的配置中心在PowerDotNet中仅仅是一个小小的模块而已)中,作为基础设施的重要组成部分,ETCD的重要性不言而喻. 本文简单 ...
- 『学了就忘』Linux文件系统管理 — 61、使用parted命令进行分区
目录 1.parted命令介绍 2.parted交互模式 3.建立分区 (1)查看分区 (2)修改成GPT分区表 (3)建立分区 (4)建立文件系统(格式化) (5)调整分区大小 (6)删除分区 1. ...
- mysql-日期时间函数大全
DAYOFWEEK(date) 返回日期date是星期几(1=星期天,2=星期一,--7=星期六,ODBC标准)mysql> select DAYOFWEEK('1998-02-03'); ...
- (数据科学学习手札132)Python+Fabric实现远程服务器连接
本文示例代码及文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 日常工作中经常需要通过SSH连接到多台远程 ...
- 解决ViewPager与ScrollView 冲突
ViewPager来实现左右滑动切换tab,如果tab的某一项中嵌入了水平可滑动的View就会让你有些不爽,比如想滑动tab项中的可水平滑动的控件,却导致tab切换. 因为Android事件机制是从父 ...
- mysql与clickhouse的字段类型对应表
- shell脚本实现openss自建CA和证书申请
#!/bin/bash # #******************************************************************** #Author: Ma Xue ...
- 基于docker 操作mysql5.7
1. 安装好 docker 2. 拉取 mysql5.7 镜像: docker pull mysql:5.7 其他版本 mysql:https://hub.docker.com/_/mysql?tab ...
- Java动态脚本Groovy,高级啊!
前言:请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i 简介: Groovy是用于Java虚拟机的一种敏捷的动态语言,它是一种成熟的面向对象编程语言,既可以用于面向对象编程,又可以用作纯粹的脚 ...