写在前面
在日常开发中,我们经常会遇到需要调用外部服务和接口的场景。外部服务对于调用者来说一般都是不可靠的,尤其是在网络环境比较差的情况下,网络抖动很容易导致请求超时等异常情况,这时候就需要使用失败重试策略重新调用 API 接口来获取。重试策略在服务治理方面也有很广泛的使用,通过定时检测,来查看服务是否存活(Active)。
Guava Retrying是一个灵活方便的重试组件,包含了多种的重试策略,而且扩展起来非常容易。
用作者的话来说:
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-retrying你可以自定义来执行重试,同时也可以监控每次重试的结果和行为,最重要的基于 Guava 风格的重试方式真的很方便。
代码示例
- 引入Guava-retry
<guava-retry.version>2.0.0guava-retry.version>
<dependency>
<groupId>com.github.rholdergroupId>
<artifactId>guava-retryingartifactId>
<version>${guava-retry.version}version>
dependency>复制代码
- 定义实现Callable接口的方法,以便Guava retryer能够调用
/**
* @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());
if(StringUtils.isEmpty(result)){
throw new RemoteException("获取OA可报销代理人接口异常");
}
List 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("更新可代理报销人异常,需要发送提醒邮件");
}复制代码
简单三步就能使用Guava Retryer优雅的实现重调方法。
接下来对其进行详细说明:
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
。每次重试之后,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<>())复制代码
参考资料:
blog.csdn.net/aitangyong/…
segmentfault.com/a/119000000…
blog.csdn.net/aitangyong/…
baijiahao.baidu.com/s?id=157532…
www.cnblogs.com/jianzh5/p/6…