应用中需要实现一个功能: 需要将
常规解决方案
try-
在包装正常上传逻辑基础上,通过判断返回结果或监听异常决定是否重试,同时为了解决立即重试的无效执行(假设异常是有外部执行不稳定导致的:网络抖动),休眠一定延迟
public vo
try-catch-redo-
上述方案还是有可能重试无效,解决这个问题尝试增加重试次数 retrycount 以及重试间隔周期 interval ,达到增加重试有效的可能性。
public void commonRetry(Map dataMap) throws InterruptedException {
Map paramMap = Maps.newHashMap();
paramMap.put("tableName", "creativeTable");
paramMap.put("ds", "20160220");
paramMap.put("dataMap", dataMap);
boolean result = false;
try {
result = uploadToOdps(paramMap);
if (!result) {
reuploadToOdps(paramMap,1000L,10);//延迟多次重试
}
} catch (Exception e) {
reuploadToOdps(paramMap,1000L,10);//延迟多次重试
}
}
复制代码
方案一和方案二存在一个问题:正常逻辑和重试逻辑强耦合,重试逻辑非常依赖正常逻辑的执行结果,对正常逻辑预期结果被动重试触发,对于重试根源往往由于逻辑复杂被淹没,可能导致后续运维对于重试逻辑要解决什么问题产生不一致理解。重试正确性难保证而且不利于运维,原因是重试设计依赖正常逻辑异常或重试根源的臆测。
优雅重试方案尝试
应用命令
命令设计模式具体定义不展开阐述,主要该方案看中
IRetry约定了上传和重试接口,其实现类OdpsRetry封装ODPS上传逻辑,同时封装
而我们的调用者LogicClient无需关注重试,通过重试者Retryer实现约定接口功能,同时 Retryer需要对重试逻辑做出响应和处理, Retryer具体重试处理又交给真正的IRtry接口的实现类OdpsRetry完成。通过采用命令模式,优雅实现正常逻辑和重试逻辑分离,同时通过构建重试者角色,实现正常逻辑和重试逻辑的分离,让重试有更好的扩展性。
使用Guava retryer优雅的实现接口重调机制
Guava retryer工具与
Maven POM 引入
2.0.0
com.
定义实现Callable接口的方法,以便Guava retryer能够调用
private static Callable
定义Retry对象并设置相关策略
Retryer retryer = RetryerBuilder.newBuilder()
//抛出runtime异常、checked异常时都会重试,但是抛出error不会重试。
.retryIfException()
//返回false也需要重试
.retryIfResult(Predicates.equalTo(false))
//重调策略
.withWaitStrategy(WaitStrategies.fixedWait(10, TimeUnit.SECONDS))
//尝试次数
.withStopStrategy(StopStrategies.stopAfterAttempt(3))
.
简单三步就能使用Guava Retryer优雅的实现重调方法。
更多特性
RetryerBuilder是一个Factory创建者,可以自定义设置重试源且支持多个重试源,可以Exception异常对象 和 自定义断言对象 ,通过 retryIfException 和retryIfResult 设置,同时支持多个且能兼容。
retryIfException :抛出runtime异常、checked异常时都会重试,但是抛出error不会重试。
retryIfRuntimeException :只会在抛runtime异常的时候才重试,checked异常和error都不重试。
retryIfExceptionOfType :允许我们只在发生特定异常的时候才重试,比如NullPointerException和IllegalStateException都属于runtime异常,也包括自定义的error 如:
# 只在抛出error重试
retryIfExceptionOfType(Error.class)
# 只有出现指定的异常的时候才重试,如:
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
接下来在Retry对象中指定监听: withRetryListener(new MyRetryListener<>())