转载自:

使用Guava retrying优雅的实现接口重调机制

Guava retrying:基于 guava 的重试组件

实际项目中,为了考虑网络抖动,加锁并发冲突等场景,我们经常需要对异常操作进行重试。优雅的重试 其实就是将业务处理逻辑和重试逻辑分离。

下面是原文地址:


API 接口调用异常和网络异常在我们日常开发中经常会遇到,这种情况下我们需要先重试几次才能将其标识为错误并在确认错误之后发送异常提醒。

Guava retrying 可以灵活的实现这一功能。Guava retrying在支持重试次数和重试频度控制基础上,能够兼容支持多个异常或者自定义实体对象的重试源定义,让重试功能有更多的灵活性。Guava retrying也是线程安全的,入口调用逻辑采用的是Java.util.concurrent.Callable的call方法。

使用Guava retrying很简单,我们只要做以下几步:

pom文件

<guava-retry.version>2.0.0</guava-retry.version>
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>${guava-retry.version}</version>
</dependency>

定义实现Callable接口的方法,以便Guava retrying能够调用

/**
* @desc 更新可代理报销人接口
* @author jianzhang11
* @date 2017/3/31 15:17
*/
private static Callable<Boolean> updateReimAgentsCall = new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
String url = ConfigureUtil.get(OaConstants.OA_REIM_AGENT);
String result = HttpMethod.post(url, new ArrayList<BasicNameValuePair>());
if(StringUtils.isEmpty(result)){
throw new RemoteException("获取OA可报销代理人接口异常");
}
List<OAReimAgents> oaReimAgents = JSON.parseArray(result, OAReimAgents.class);
if(CollectionUtils.isNotEmpty(oaReimAgents)){
CacheUtil.put(Constants.REIM_AGENT_KEY,oaReimAgents);
return true;
}
return false;
}
};

定义Retry对象并设置相关策略

Retryer<Boolean> retryer = RetryerBuilder
.<Boolean>newBuilder()
// 抛出runtime异常、checked异常时都会重试,但是抛出error不会重试。
.retryIfException()
// 自定义 指定返回值 也需要重试:返回false也需要重试
.retryIfResult(Predicates.equalTo(false))
// 重试时间间隔
.withWaitStrategy(WaitStrategies.fixedWait(10, TimeUnit.SECONDS))
// 尝试次数
.withStopStrategy(StopStrategies.stopAfterAttempt(3))
.build(); try {
retryer.call(updateReimAgentsCall);
} catch (ExecutionException e) {
//e.printStackTrace();
} catch (RetryException e) {
logger.error("更新可代理报销人异常,需要发送提醒邮件");
}

API说明

上面简单三步就能使用Guava retrying优雅的实现重调方法。接下来对其进行详细说明: 

  • RetryerBuilder是一个factory创建者,可以定制设置重试源且可以支持多个重试源,可以配置重试次数或重试超时时间,以及可以配置等待时间间隔,创建重试者Retryer实例。
  • RetryerBuilder的重试源支持Exception异常对象和自定义对象,通过retryIfException 和 retryIfResult设置,同时支持多个且能互相兼容。
  • retryIfException,抛出runtime异常、checked异常时都会重试,但是抛出error不会重试。
  • retryIfRuntimeException只会在抛runtime异常的时候才重试,checked异常和error都不重试。
  • retryIfExceptionOfType允许我们只在发生特定异常的时候才重试,比如 NullPointerException 和 IllegalStateException 都属于runtime异常,也包括自定义的error

    如:
.retryIfExceptionOfType(Error.class)// 只在抛出error重试

当然我们还可以在只有出现指定的异常的时候才重试,如:

.retryIfExceptionOfType(IllegalStateException.class)
.retryIfExceptionOfType(NullPointerException.class)

或者通过Predicate实现:

.retryIfException(Predicates.or(Predicates.instanceOf(NullPointerException.class),
Predicates.instanceOf(IllegalStateException.class)))

retryIfResult可以指定你的Callable方法在返回值的时候进行重试,如:

// 返回false重试
.retryIfResult(Predicates.equalTo(false))
//以_error结尾才重试
.retryIfResult(Predicates.containsPattern("_error$"))
  • RetryListener:自定义重试监听器,可以用于异步记录错误日志,具体实例如下:

当发生重试之后,假如我们需要做一些额外的处理动作,比如发个告警邮件啥的,那么可以使用RetryListener。每次重试之后,guava-retrying会自动回调我们注册的监听。可以注册多个RetryListener,会按照注册顺序依次调用。

import com.github.rholder.retry.Attempt;
import com.github.rholder.retry.RetryListener;
import java.util.concurrent.ExecutionException; public class MyRetryListener<Boolean> implements RetryListener { @Override
public <Boolean> void onRetry(Attempt<Boolean> attempt) { // 第几次重试,(注意:第一次重试其实是第一次调用)
System.out.print("[retry]time=" + attempt.getAttemptNumber()); // 距离第一次重试的延迟
System.out.print(",delay=" + attempt.getDelaySinceFirstAttempt()); // 重试结果: 是异常终止, 还是正常返回
System.out.print(",hasException=" + attempt.hasException());
System.out.print(",hasResult=" + attempt.hasResult()); // 是什么原因导致异常
if (attempt.hasException()) {
System.out.print(",causeBy=" + attempt.getExceptionCause().toString());
} else {
// 正常返回时的结果
System.out.print(",result=" + attempt.getResult());
} // bad practice: 增加了额外的异常处理代码
try {
Boolean result = attempt.get();
System.out.print(",rude get=" + result);
} catch (ExecutionException e) {
System.err.println("this attempt produce exception." + e.getCause().toString());
} System.out.println();
}
}

接下来在Retry对象中指定监听:

.withRetryListener(new MyRetryListener<>())

效果如下:

  • StopStrategy:停止重试策略,提供三种:
  1. StopAfterDelayStrategy 设定一个最长允许的执行时间;比如设定最长执行10s,无论任务执行次数,只要重试的时候超出了最长时间,则任务终止,并返回重试异常RetryException。
  2. NeverStopStrategy 不停止,用于需要一直轮训知道返回期望结果的情况。
  3. StopAfterAttemptStrategy 设定最大重试次数,如果超出最大重试次数则停止重试,并返回重试异常。
  • WaitStrategy:等待时长策略(控制时间间隔),返回结果为下次执行时长:
  1. FixedWaitStrategy 固定等待时长策略。
  2. RandomWaitStrategy 随机等待时长策略(可以提供一个最小和最大时长,等待时长为其区间随机值)。
  3. IncrementingWaitStrategy 递增等待时长策略(提供一个初始值和步长,等待时间随重试次数增加而增加)。
  4. ExponentialWaitStrategy 指数等待时长策略。
  5. FibonacciWaitStrategy Fibonacci 等待时长策略。
  6. ExceptionWaitStrategy 异常时长等待策略。
  7. CompositeWaitStrategy 复合时长等待策略。

使用Guava retryer优雅的实现接口重试机制的更多相关文章

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

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

  2. Java之Retry重试机制详解

    应用中需要实现一个功能: 需要将数据上传到远程存储服务,同时在返回处理成功情况下做其他操作.这个功能不复杂,分为两个步骤:第一步调用远程的Rest服务上传数据后对返回的结果进行处理:第二步拿到第一步结 ...

  3. Guava Retryer实现接口重试

    前言 小黑在开发中遇到个问题,我负责的模块需要调用某个三方服务接口查询信息,查询结果直接影响后续业务逻辑的处理: 这个接口偶尔会因网络问题出现超时,导致我的业务逻辑无法继续处理: 这个问题该如何解决呢 ...

  4. guava的重试机制guava-retrying使用

    1,添加maven依赖 <dependency> <groupId>com.github.rholder</groupId> <artifactId>g ...

  5. 优雅实现INotifyPropertyChanged接口——利用Lambda表达式

    原文:优雅实现INotifyPropertyChanged接口--利用Lambda表达式 参考文章 在14年的时候,曾经读过上面的参考文章,不过当时并没有怎么理解,慢慢地也就将这篇文章忘诸脑后了. 直 ...

  6. spring-retry 重试机制

    业务场景 应用中需要实现一个功能: 需要将数据上传到远程存储服务,同时在返回处理成功情况下做其他操作.这个功能不复杂,分为两个步骤:第一步调用远程的Rest服务逻辑包装给处理方法返回处理结果:第二步拿 ...

  7. springcloud之Feign、ribbon设置超时时间和重试机制的总结

    一 超时时间配置 如果在一个微服务当中对同一个接口同时配置了Hystrix与ribbon两个超时时间,则在接口调用的时候,两个计时器会同时读秒. 比如,访问一个接口需要2秒,你的ribbon配置的超时 ...

  8. Java重试机制

    重试作用: 对于重试是有场景限制的,不是什么场景都适合重试,比如参数校验不合法.写操作等(要考虑写是否幂等)都不适合重试. 远程调用超时.网络突然中断可以重试.在微服务治理框架中,通常都有自己的重试与 ...

  9. SpringCloud微服务实战——搭建企业级开发框架(十三):OpenFeign+Ribbon实现高可用重试机制

      Spring Cloud OpenFeign 默认是使用Ribbon实现负载均衡和重试机制的,虽然Feign有自己的重试机制,但该功能在Spring Cloud OpenFeign基本用不上,除非 ...

随机推荐

  1. 【BZOJ2329】括号修复(Splay)

    [BZOJ2329]括号修复(Splay) 题面 BZOJ 洛谷 题解 本来想着用线段树来写 但是有一个区间翻转 所以不能用线段树了,就只能用平衡树 然后直接\(Splay\)就好了 注意一下几个标记 ...

  2. Python之旅:数字、字符串

    一 数字 整型与浮点型 #整型int 作用:年纪,等级,身份证号,qq号等整型数字相关 定义: age=10 #本质age=int(10) #浮点型float 作用:薪资,身高,体重,体质参数等浮点数 ...

  3. Codeforces Round #301 (Div. 2)A B C D 水 模拟 bfs 概率dp

    A. Combination Lock time limit per test 2 seconds memory limit per test 256 megabytes input standard ...

  4. 12.UiAutomator 获取系统信息

    一.Build构建信息 1.build类: Build类提供了硬件厂商.编号.序列号.SDK版本等重要信息. 类名:android.os.Build 常量名 说明 BOARD 底层板名称 BOOTLO ...

  5. [LeetCode] 25. Reverse Nodes in k-Group ☆☆☆

    Given a linked list, reverse the nodes of a linked list k at a time and return its modified list. k  ...

  6. AngularJs编辑器

    问题:在使用过程中遇到的问题就是:angularJS绑定不上值.后来找到一种可以用的方式. html页 <div class="all"> <script typ ...

  7. 支付宝Android接口4.0以上报错Failure calling remote service

    很坑爹的问题,4.0一下没问题,完全按照接口文件写的.网上一查,很多人遇到.最好直接在4.2下调试,看看报错问题,然后度娘. 将RSA文件中的加上“BC”后测试通过...NND PKCS8Encode ...

  8. JavaScript:自动生成博文目录导航

    感谢 孤傲苍狼 分享了 自动生成博文目录的方法,本文仅作存档使用. 图 1:效果预览 CSS 样式 #TOCbar{ font-size:12px; text-align:left; position ...

  9. 【洛谷P2015】二叉苹果树

    题目描述 有一棵苹果树,如果树枝有分叉,一定是分2叉(就是说没有只有1个儿子的结点) 这棵树共有N个结点(叶子点或者树枝分叉点),编号为1-N,树根编号一定是1. 我们用一根树枝两端连接的结点的编号来 ...

  10. 学习webpack

    前言 webpack前端工程中扮演的角色越来越重要,它也是前端工程化很重要的一环.本文将和大家一起按照项目流程学习使用wbepack,妈妈再也不用担心我不会使用webpack,哪里不会看哪里.这是一个 ...