重试利器之Guava-Retryer

写在前面

在平常开发中,咱们常常会遇到须要调用外部服务和接口的场景。外部服务对于调用者来讲通常都是不可靠的,尤为是在网络环境比较差的状况下,网络抖动很容易致使请求超时等异常状况,这时候就须要使用失败重试策略从新调用 API 接口来获取。重试策略在服务治理方面也有很普遍的使用,经过定时检测,来查看服务是否存活(Active)。css

Guava Retrying是一个灵活方便的重试组件,包含了多种的重试策略,并且扩展起来很是容易。html

用做者的话来讲:java

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.git

使用Guava-retrying你能够自定义来执行重试,同时也能够监控每次重试的结果和行为,最重要的基于 Guava 风格的重试方式真的很方便。github

代码示例

  • 引入Guava-retry
<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 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<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("更新可代理报销人异常,须要发送提醒邮件");
        }复制代码

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

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

如:segmentfault

.retryIfExceptionOfType(Error.class)// 只在抛出error重试复制代码

固然咱们还能够在只有出现指定的异常的时候才重试,如:网络

.retryIfExceptionOfType(IllegalStateException.class)  
.retryIfExceptionOfType(NullPointerException.class)复制代码

或者经过Predicate实现less

.retryIfException(Predicates.or(Predicates.instanceOf(NullPointerException.class),  
                Predicates.instanceOf(IllegalStateException.class)))复制代码

retryIfResult能够指定你的Callable方法在返回值的时候进行重试,如ide

// 返回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…

相关文章
相关标签/搜索