zoukankan      html  css  js  c++  java
  • Spring错误异常重试框架guava-retrying

    官网:https://github.com/rholder/guava-retrying

    Maven:https://mvnrepository.com/artifact/com.github.rholder/guava-retrying

    下面示例是基于Spring Boot的,但是都可以用于Spring项目。目前最新版是2.0.0。

    集成步骤:

    POM引入:

            <!-- https://mvnrepository.com/artifact/com.github.rholder/guava-retrying -->
            <dependency>
                <groupId>com.github.rholder</groupId>
                <artifactId>guava-retrying</artifactId>
                <version>2.0.0</version>
            </dependency>

    直接一个类里面进行操作,基于匿名内部类实现。

    package com.jsoft.springboottest.springboottest1.controller;
    
    import java.io.IOException;
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.TimeUnit;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.github.rholder.retry.RetryException;
    import com.github.rholder.retry.Retryer;
    import com.github.rholder.retry.RetryerBuilder;
    import com.github.rholder.retry.StopStrategies;
    import com.github.rholder.retry.WaitStrategies;
    import com.google.common.base.Predicates;
    
    @RestController
    public class TestController {
        
        private static final Logger logger = LoggerFactory.getLogger(TestController.class);
        
        @RequestMapping("/show")
        public String show(){        
            Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
                    .retryIfResult(Predicates.<Boolean>isNull())// 设置自定义段元重试源
                    .retryIfExceptionOfType(Exception.class)// 设置异常重试源
                    .retryIfRuntimeException()// 设置异常重试源
                    .withStopStrategy(StopStrategies.stopAfterAttempt(5))// 设置重试5次,同样可以设置重试超时时间
                    .withWaitStrategy(WaitStrategies.fixedWait(5L, TimeUnit.SECONDS))// 设置每次重试间隔,5秒
                    .build();
            try {
                retryer.call(new Callable<Boolean>() {
                    int i = 0;
    
                    @Override
                    public Boolean call() throws Exception {
                        i++;
                        logger.info("第{}次执行!", i);
                        // do something
                        if (i<6) {// 模拟错2次
                            logger.info("模拟执行失败!");
                            throw new IOException("异常");
                        }
                        logger.info("模拟执行成功!");
                        return true;                   
                    }
                });
            } catch (RetryException e) {
                logger.info("超过重试次数", e);
            } catch (ExecutionException e) {
                logger.info("重试框架异常", e);
            }
            
            return "Hello World";        
        }
    
    
    }

    示例工程:https://github.com/easonjim/5_java_example/tree/master/springboottest/springboottest5

    详细介绍:

    使用场景

    在日常开发中,我们经常会遇到需要调用外部服务和接口的场景。外部服务对于调用者来说一般都是不可靠的,尤其是在网络环境比较差的情况下,网络抖动很容易导致请求超时等异常情况,这时候就需要使用失败重试策略重新调用 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-retrying的使用方式:

    如果抛出IOException则重试,如果返回结果为null或者等于2则重试,固定等待时长为300 ms,最多尝试3次; 

    Callable<Integer> task = new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            return 2;
        }
    };
    
    Retryer<Integer> retryer = RetryerBuilder.<Integer>newBuilder()
            .retryIfResult(Predicates.<Integer>isNull())
            .retryIfResult(Predicates.equalTo(2))
            .retryIfExceptionOfType(IOException.class)
            .withStopStrategy(StopStrategies.stopAfterAttempt(3))
            .withWaitStrategy(WaitStrategies.fixedWait(300, TimeUnit.MILLISECONDS))
            .build();
    try {
        retryer.call(task);
    } catch (ExecutionException e) {
        e.printStackTrace();
    } catch (RetryException e) {
        e.printStackTrace();
    }

    出现异常则执行重试,每次任务执行最长执行时间限定为 3 s,重试间隔时间初始为 3 s,最多重试 1 分钟,随着重试次数的增加每次递增 1 s,每次重试失败,打印日志;

    @Override
    public Integer call() throws Exception {
            return 2;
        }
    };
    
    Retryer<Integer> retryer = RetryerBuilder.<Integer>newBuilder()
            .retryIfException()
            .withStopStrategy(StopStrategies.stopAfterDelay(30,TimeUnit.SECONDS))
            .withWaitStrategy(WaitStrategies.incrementingWait(3, TimeUnit.SECONDS,1,TimeUnit.SECONDS))
            .withAttemptTimeLimiter(AttemptTimeLimiters.<Integer>fixedTimeLimit(3,TimeUnit.SECONDS))
            .withRetryListener(new RetryListener() {
                @Override
                public <V> void onRetry(Attempt<V> attempt) {
                    if (attempt.hasException()){
                        attempt.getExceptionCause().printStackTrace();
                    }
                }
            })
            .build();
    try {
        retryer.call(task);
    } catch (ExecutionException e) {
        e.printStackTrace();
    } catch (RetryException e) {
        e.printStackTrace();
    } 

    核心执行逻辑分析:

    long startTime = System.nanoTime();
    for (int attemptNumber = 1; ; attemptNumber++) {
        Attempt<V> attempt;
        try {
            // 执行成功
            V result = attemptTimeLimiter.call(callable);
            attempt = new ResultAttempt<V>(result, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));
        } catch (Throwable t) {
            // 执行失败
            attempt = new ExceptionAttempt<V>(t, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));
        }
        // 监听器处理
        for (RetryListener listener : listeners) {
            listener.onRetry(attempt);
        }
        // 是否符合终止策略
        if (!rejectionPredicate.apply(attempt)) {
            return attempt.get();
        }
        // 是否符合停止策略
        if (stopStrategy.shouldStop(attempt)) {
            throw new RetryException(attemptNumber, attempt);
        } else {
            // 计算下次重试间隔时间
            long sleepTime = waitStrategy.computeSleepTime(attempt);
            try {
                blockStrategy.block(sleepTime);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RetryException(attemptNumber, attempt);
            }
        }
    }

     依赖引入

    <dependency>
          <groupId>com.github.rholder</groupId>
          <artifactId>guava-retrying</artifactId>
          <version>2.0.0</version>
    </dependency>

    主要接口及策略介绍:

    Attempt:一次执行任务;

    AttemptTimeLimiter:单次任务执行时间限制(如果单次任务执行超时,则终止执行当前任务);

    BlockStrategies:任务阻塞策略(通俗的讲就是当前任务执行完,下次任务还没开始这段时间做什么……),默认策略为:BlockStrategies.THREAD_SLEEP_STRATEGY 也就是调用 Thread.sleep(sleepTime);

    RetryException:重试异常;

    RetryListener:自定义重试监听器,可以用于异步记录错误日志;

    StopStrategy:停止重试策略,提供三种:

    • StopAfterDelayStrategy :设定一个最长允许的执行时间;比如设定最长执行10s,无论任务执行次数,只要重试的时候超出了最长时间,则任务终止,并返回重试异常RetryException
    • NeverStopStrategy :不停止,用于需要一直轮训知道返回期望结果的情况;
    • StopAfterAttemptStrategy :设定最大重试次数,如果超出最大重试次数则停止重试,并返回重试异常;

    WaitStrategy:等待时长策略(控制时间间隔),返回结果为下次执行时长:

    • FixedWaitStrategy:固定等待时长策略;
    • RandomWaitStrategy:随机等待时长策略(可以提供一个最小和最大时长,等待时长为其区间随机值)
    • IncrementingWaitStrategy:递增等待时长策略(提供一个初始值和步长,等待时间随重试次数增加而增加)
    • ExponentialWaitStrategy:指数等待时长策略;
    • FibonacciWaitStrategy :Fibonacci 等待时长策略;
    • ExceptionWaitStrategy :异常时长等待策略;
    • CompositeWaitStrategy :复合时长等待策略;

    参考:

    http://blog.csdn.net/aitangyong/article/details/53894997

    https://segmentfault.com/a/1190000006918410

    http://blog.csdn.net/aitangyong/article/details/53886293

    http://baijiahao.baidu.com/s?id=1575327487081031&wfr=spider&for=pc

    http://www.cnblogs.com/jianzh5/p/6651799.html

    http://lintrip.com/2016/05/27/guava-retry/(以上部分内容转自此篇文章)

  • 相关阅读:
    《程序是怎样跑起来的》读书笔记——第十一章 硬件控制方法
    《程序是怎样跑起来的》读书笔记——第十章 通过汇编语言了解程序的实际构成
    《程序是怎样跑起来的》读书笔记——第九章 操作系统和应用的关系
    《程序是怎样跑起来的》读书笔记——第八章 从源文件到可执行文件
    《程序是怎样跑起来的》读书笔记——第七章 程序是在何种环境中运行的
    Linux入门之常用命令(15) lsof
    关于volatile 最完整的一篇文章
    Redis缓冲区设置
    设计模式之【抽象工厂模式】
    设计模式之【工厂模式】
  • 原文地址:https://www.cnblogs.com/EasonJim/p/7766881.html
Copyright © 2011-2022 走看看