应用中须要实现一个功能: 须要将数据上传到远程存储服务,同时在返回处理成功状况下作其余操做。这个功能不复杂,分为两个步骤:第一步调用远程的Rest服务逻辑包装给处理方法返回处理结果;第二步拿到第一步结果或者捕捉异常,若是出现错误或异常实现重试上传逻辑,不然继续逻辑操做。java
这个问题的技术点在于可以触发重试,以及重试状况下逻辑有效执行。程序员
包装正常上传逻辑基础上,经过判断返回结果或监听异常决策是否重试,同时为了解决当即重试的无效执行(假设异常是有外部执行不稳定致使的),休眠必定延迟时间从新执行功能逻辑。算法
public void commonRetry(Map<String, Object> dataMap) throws InterruptedException {
Map<String, Object> paramMap = Maps.newHashMap();
paramMap.put("tableName", "creativeTable");
paramMap.put("ds", "20160220");
paramMap.put("dataMap", dataMap);
boolean result = false;
try {
result = uploadToOdps(paramMap);
if (!result) {
Thread.sleep(1000);
uploadToOdps(paramMap); //一次重试
}
} catch (Exception e) {
Thread.sleep(1000);
uploadToOdps(paramMap);//一次重试
}
}
复制代码
上述方案仍是有可能重试无效,解决这个问题尝试增长重试次数retrycount以及重试间隔周期interval,达到增长重试有效的可能性。spring
public void commonRetry(Map<String, Object> dataMap) throws InterruptedException {
Map<String, Object> 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);//延迟屡次重试
}
}
复制代码
方案一和方案二存在一个问题:正常逻辑和重试逻辑强耦合,重试逻辑很是依赖正常逻辑的执行结果,对正常逻辑预期结果被动重试触发,对于重试根源每每因为逻辑复杂被淹没,可能致使后续运维对于重试逻辑要解决什么问题产生不一致理解。重试正确性难保证并且不利于运维,缘由是重试设计依赖正常逻辑异常或重试根源的臆测。设计模式
那有没有能够参考的方案实现正常逻辑和重试逻辑解耦,同时可以让重试逻辑有一个标准化的解决思路?答案是有:那就是基于代理设计模式的重试工具,咱们尝试使用相应工具来重构上述场景。安全
命令设计模式具体定义不展开阐述,主要该方案看中命令模式可以经过执行对象完成接口操做逻辑,同时内部封装处理重试逻辑,不暴露实现细节,对于调用者来看就是执行了正常逻辑,达到解耦的目标,具体看下功能实现。(类图结构)bash
IRetry约定了上传和重试接口,其实现类OdpsRetry封装ODPS上传逻辑,同时封装重试机制和重试策略。与此同时使用recover方法在结束执行作恢复操做。服务器
而咱们的调用者LogicClient无需关注重试,经过重试者Retryer实现约定接口功能,同时 Retryer须要对重试逻辑作出响应和处理, Retryer具体重试处理又交给真正的IRtry接口的实现类OdpsRetry完成。经过采用命令模式,优雅实现正常逻辑和重试逻辑分离,同时经过构建重试者角色,实现正常逻辑和重试逻辑的分离,让重试有更好的扩展性。并发
spring-retry是一个开源工具包,目前可用的版本为1.1.2.RELEASE,该工具把重试操做模板定制化,能够设置重试策略和回退策略。同时重试执行实例保证线程安全,具体场景操做实例以下:app
public void upload(final Map<String, Object> map) throws Exception {
// 构建重试模板实例
RetryTemplate retryTemplate = new RetryTemplate();
// 设置重试策略,主要设置重试次数
SimpleRetryPolicy policy = new SimpleRetryPolicy(3, Collections.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true));
// 设置重试回退操做策略,主要设置重试间隔时间
FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
fixedBackOffPolicy.setBackOffPeriod(100);
retryTemplate.setRetryPolicy(policy);
retryTemplate.setBackOffPolicy(fixedBackOffPolicy);
// 经过RetryCallback 重试回调实例包装正常逻辑逻辑,第一次执行和重试执行执行的都是这段逻辑
final RetryCallback<Object, Exception> retryCallback = new RetryCallback<Object, Exception>() {
//RetryContext 重试操做上下文约定,统一spring-try包装
public Object doWithRetry(RetryContext context) throws Exception {
System.out.println("do some thing");
Exception e = uploadToOdps(map);
System.out.println(context.getRetryCount());
throw e;//这个点特别注意,重试的根源经过Exception返回
}
};
// 经过RecoveryCallback 重试流程正常结束或者达到重试上限后的退出恢复操做实例
final RecoveryCallback<Object> recoveryCallback = new RecoveryCallback<Object>() {
public Object recover(RetryContext context) throws Exception {
System.out.println("do recory operation");
return null;
}
};
try {
// 由retryTemplate 执行execute方法开始逻辑执行
retryTemplate.execute(retryCallback, recoveryCallback);
} catch (Exception e) {
e.printStackTrace();
}
}
复制代码
简单剖析下案例代码,RetryTemplate 承担了重试执行者的角色,它能够设置SimpleRetryPolicy(重试策略,设置重试上限,重试的根源实体),FixedBackOffPolicy(固定的回退策略,设置执行重试回退的时间间隔)。 RetryTemplate经过execute提交执行操做,须要准备RetryCallback 和RecoveryCallback 两个类实例,前者对应的就是重试回调逻辑实例,包装正常的功能操做,RecoveryCallback实现的是整个执行操做结束的恢复操做实例。
RetryTemplate的execute 是线程安全的,实现逻辑使用ThreadLocal保存每一个执行实例的RetryContext执行上下文。
Spring-retry工具虽能优雅实现重试,可是存在两个不友好设计:一个是 重试实体限定为Throwable子类,说明重试针对的是可捕捉的功能异常为设计前提的,可是咱们但愿依赖某个数据对象实体做为重试实体,但Sping-retry框架必须强制转换为Throwable子类。另外一个就是重试根源的断言对象使用的是doWithRetry的Exception 异常实例,不符合正常内部断言的返回设计。
Spring Retry提倡以注解的方式对方法进行重试,重试逻辑是同步执行的,重试的“失败”针对的是Throwable,若是你要以返回值的某个状态来断定是否须要重试,可能只能经过本身判断返回值而后显式抛出异常了。
“抽象”是每一个程序员必备的素质。对于资质平平的我来讲,没有比模仿与理解优秀源码更好地进步途径了吧。为此,我将其核心逻辑重写了一遍…下面就看看Spring Retry对于“重试”的抽象。
while(someCondition()) {
try{
doSth();
break;
} catch(Throwable th) {
modifyCondition();
wait();
}
}
if(stillFail) {
doSthWhenStillFail();
}复制代码
同步重试代码基本能够表示为上述,可是Spring Retry对其进行了很是优雅地抽象,虽然主要逻辑不变,可是看起来倒是舒服多了。主要的接口抽象以下图所示:
Guava retryer工具与spring-retry相似,都是经过定义重试者角色来包装正常逻辑重试,可是Guava retryer有更优的策略定义,在支持重试次数和重试频度控制基础上,可以兼容支持多个异常或者自定义实体对象的重试源定义,让重试功能有更多的灵活性。Guava Retryer也是线程安全的,入口调用逻辑采用的是Java.util.concurrent.Callable的call方法,示例代码以下:
public void uploadOdps(final Map<String, Object> map) {
// RetryerBuilder 构建重试实例 retryer,能够设置重试源且能够支持多个重试源,能够配置重试次数或重试超时时间,以及能够配置等待时间间隔
Retryer<Boolean> retryer = RetryerBuilder.<Boolean> newBuilder()
.retryIfException().//设置异常重试源
retryIfResult(new Predicate<Boolean>() {//设置自定义段元重试源,
@Override
public boolean apply(Boolean state) {//特别注意:这个apply返回true说明须要重试,与操做逻辑的语义要区分
return true;
}
})
.withStopStrategy(StopStrategies.stopAfterAttempt(5))//设置重试5次,一样能够设置重试超时时间
.withWaitStrategy(WaitStrategies.fixedWait(100L, TimeUnit.MILLISECONDS)).build();//设置每次重试间隔
try {
//重试入口采用call方法,用的是java.util.concurrent.Callable<V>的call方法,因此执行是线程安全的
boolean result = retryer.call(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
try {
//特别注意:返回false说明无需重试,返回true说明须要继续重试
return uploadToOdps(map);
} catch (Exception e) {
throw new Exception(e);
}
}
});
} catch (ExecutionException e) {
} catch (RetryException ex) {
}
}
复制代码
示例代码原理分析:
RetryerBuilder是一个factory建立者,能够定制设置重试源且能够支持多个重试源,能够配置重试次数或重试超时时间,以及能够配置等待时间间隔,建立重试者Retryer实例。
RetryerBuilder的重试源支持Exception异常对象 和自定义断言对象,经过retryIfException 和retryIfResult设置,同时支持多个且能兼容。
RetryerBuilder的等待时间和重试限制配置采用不一样的策略类实现,同时对于等待时间特征能够支持无间隔和固定间隔方式。
Retryer 是重试者实例,经过call方法执行操做逻辑,同时封装重试源操做。
https://blog.csdn.net/paul_wei2008/article/details/53871442