guava之Retryer

Retryer看结构上是比较简单的,但是就是用这个简单结构实现了比较通用的重试框架。

它之所以用入册简单的结构能实现通用的重试框架,它对重试这件事情抽象的是很好的,所以我们看一下重试需要哪些事情:

  1. 需要用一个机制来表达重试的任务。Retryer采用的是使用Callable这个接口来表示一个重试任务。并且通过AttemptTimeLimiter封装实现了多种执行任务的方式。
  2. 需要一种判断什么时候重试、什么时候结束。Retryer使用了Predicate这个函数式接口来让用户来决定什么时候表示执行成功,什么时候执行失败需要重试。并提供了一下常用的,比如:Callable任务抛出异常时重试、返回指定结果时重试以及各种但条件的Or以及And的组合。
  3. 如果重试一直不成功,怎么办?Retryer通过StopStrategy封装,限定了重试的次数。
  4. 重试间隔:一个是怎么指定间隔,另外一个是这个间隔期间,线程该干啥?Retryer使用WaitStrategy来封装重试间隔、使用BlockStrategy来指明重试间隔线程应该干啥,要扩展,那就需要去重写Retryer#call()方法。

而且可以发现,这些机制都是面向接口的,Retryer提供了常见实现,并且允许你去扩展。不过StopStrategy除外,只能是这三种,因为在Retryer#call()中就只是实现了这三种策略。

整个Retryer的最重要的就一个call()方法:

 public V call(Callable<V> callable) throws ExecutionException, RetryException {
        long startTime = System.nanoTime();// 这个是用于waitStrategy=StopAfterDelayStrategy的时间
        for (int attemptNumber = 1; ; attemptNumber++) {//attemptNumber是用于waitStrategy=StopAfterAttemptStrategy的重试次数
            Attempt<V> attempt;
            try {
                V result = attemptTimeLimiter.call(callable);// AttemptTimeLimiter执行Callable任务
                attempt = new Retryer.ResultAttempt<V>(result);// Callable结果封装
            } catch (Throwable t) {
                attempt = new Retryer.ExceptionAttempt<V>(t);
            }
            if (!rejectionPredicate.apply(attempt)) {// Predicate决定是否要重试
                return attempt.get();// Predicate返回true表示成功,则不再重试,返回结果。
            }
            long delaySinceFirstAttemptInMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
            if (stopStrategy.shouldStop(attemptNumber, delaySinceFirstAttemptInMillis)) {// stopStrategy决定是否要停止重试
                throw new RetryException(attemptNumber, attempt);
            } else {
                //waitStrategy获得重试间隔时间。
                long sleepTime = waitStrategy.computeSleepTime(attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));
                try {
                    blockStrategy.block(sleepTime);//如果要重试,在重试间隔中blockStrategy告诉当前线程该干啥,默认只是实现了一种,就是休眠。
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    throw new RetryException(attemptNumber, attempt);
                }
            }
        }
    }

Retryer#call()方法申明抛出了两个异常:ExecutionException, RetryException ,从元马上看当达到重试次数后还没有成功会抛出RetryException,但是没有发现哪里申明抛出了ExecutionException。这个其实我觉得是Retryer一个不太好的地方,这个异常其实是Future#get()方法申明抛出的,所以只有在使用attemptTimeLimiter=FixedAttemptTimeLimit的时候,Callable任务是在其内部写死的一个线程池中执行,然后会使用Future#get()方法等待提交给线程池的任务执行完成,而这个地方可能会抛出Execution。如果我们没有自定attemptTimeLimiter,默认使用的NoAttemptTimeLimiter,就不会抛出ExecutionException。
ps:AttemptTimeLimiter是用来执行Callable的,但为啥叫TimeLimiter?其实就是想要表达的是执行Callable任务的事件限制:NoAttemptTimeLimiter就是没有事件限制,而FixedAttemptTimeLimit就是固定时间限制,而它实现Callable时间限制就是利用了Future#get(timeout),所以FixedAttemptTimeLimit内部是提交给了一个线程池(Executors.newCachedThreadPool())去执行,然后用Future#get(timeOut)来获取结果,从而实现控制Callable执行时间的目的。

一个使用Retryer的demo:

  // Retry,其中泛型就是Callable的返回值类型
        Retryer<String> retryer = RetryerBuilder.<String>newBuilder()

                // 在Predicates静态类中有实现了一些,但没有包含我们常用的,在RetryerBuilder中又实现了一些,包含了一些常用的:异常重试、返回指定值重试等
                // ps:RetryerBuilder中只是实现了多种条件的or(将多个Predicate放在链表中,调用的时候遍历,有一个返回true就返回true,类似or的效果)
                // Predicates实现了or以及and,视具体情况选用,或者自己去实现一个。其实RetryerBuilder能满足绝大多数需要。
                .retryIfException()// Predicate<Attempt<String>> 决定什么是失败,重试。
                .retryIfResult(new Predicate<String>() {
                    @Override
                    public boolean apply(@Nullable String input) {
                        return input.equals("aaa");
                    }
                })// 这两个是或者的关系:实际任务抛出异常,或者返回值是aaaa,都会进行重试。如果不设置就是总是成功,即不会重试。
                .withStopStrategy(StopStrategies.stopAfterAttempt(3))// 重试结束限制。不设置就是NeverStopStrategy,无限重试
                .withWaitStrategy(WaitStrategies.fixedWait(200, TimeUnit.MICROSECONDS))// 重试间隔。不设置默认FixedWaitStrategy(0),其实就是不休眠
                //.withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(1,TimeUnit.SECONDS))  //不设置就是AttemptTimeLimiters
                //.withAttemptTimeLimiter(new KAttemptTimeLimiter(3))

                //.withBlockStrategy(BlockStrategies.threadSleepStrategy()) //不设置就是ThreadSleepStrategy(线程休眠)
                .build();


        // 实际使用
        try {
            String result = retryer.call(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    System.out.println("我在重试。。。");
                    //System.out.println(Thread.currentThread().getId() + "  " + Thread.currentThread().getName());
                    throw new RuntimeException("a");
                    //return "aaa";
                }
            });
            System.out.println(result);

            // 这个是FixedAttemptTimeLimit才会抛出的异常,在feature.get()抛的异常,而且重试不能是抛出异常重试,否则也进不到这来,因为有异常的时候优先重试了,然后重试不陈宫抛出的是RetryException
        }catch (ExecutionException e ){
            System.out.println("ExecutionException:"+e);
        }catch (RetryException e){
            System.out.println("RetryException:"+e);
        }

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值