前言
guava-retrying github地址:https://github.com/rholder/guava-retrying
guava-retrying是谷歌的Guava库的一个小扩展,允许为任意函数调用创建可配置的重试策略,比如与正常运行时间不稳定的远程服务对话的函数调用。
在日常开发中,尤其是在微服务盛行的时代下,我们在调用外部接口时,经常会因为第三方接口超时、限流等问题从而造成接口调用失败,那么此时我们通常会对接口进行重试,那么问题来了,如何重试呢?该重试几次呢?如果要设置重试时间超过多长时间后还不成功就不重试了该怎么做呢?所幸guava-retrying为我们提供了强大而简单易用的重试框架guava-retrying。
一.pom依赖
<dependency><groupId>com.github.rholder</groupId><artifactId>guava-retrying</artifactId><version>2.0.0</version></dependency>
二.使用示例
我们可以通过RetryerBuilder来构造一个重试器,通过RetryerBuilder可以设置什么时候需要重试(即重试时机)、停止重试策略、失败等待时间间隔策略、任务执行时长限制策略
先看一个简单的例子:
privateintinvokeCount=0;publicintrealAction(intnum){invokeCount++;System.out.println(String.format("当前执行第%d次,num:%d",invokeCount,num));if(num<=0){thrownewIllegalArgumentException();}returnnum;}@TestpublicvoidguavaRetryTest001(){Retryer<Integer>retryer=RetryerBuilder.<Integer>newBuilder()//非正数进行重试.retryIfRuntimeException()//偶数则进行重试.retryIfResult(result->result%2==0)//设置最大执行次数3次.withStopStrategy(StopStrategies.stopAfterAttempt(3)).build();try{invokeCount=0;retryer.call(()->realAction(0));}catch(Exceptione){System.out.println("执行0,异常:"+e.getMessage());}try{invokeCount=0;retryer.call(()->realAction(1));}catch(Exceptione){System.out.println("执行1,异常:"+e.getMessage());}try{invokeCount=0;retryer.call(()->realAction(2));}catch(Exceptione){System.out.println("执行2,异常:"+e.getMessage());}}
输出:
当前执行第1次,num:0当前执行第2次,num:0当前执行第3次,num:0执行0,异常:Retryingfailedtocompletesuccessfullyafter3attempts.当前执行第1次,num:1当前执行第1次,num:2当前执行第2次,num:2当前执行第3次,num:2执行2,异常:Retryingfailedtocompletesuccessfullyafter3attempts.
三.重试时机
RetryerBuilder的retryIfXXX()方法用来设置在什么情况下进行重试,总体上可以分为根据执行异常进行重试和根据方法执行结果进行重试两类。
3.1 根据异常进行重试
3.2 根据返回结果进行重试
retryIfResult(@Nonnull Predicate
四.停止重试策略StopStrategy
停止重试策略用来决定什么时候不进行重试,其接口com.github.rholder.retry.StopStrategy,停止重试策略的实现类均在com.github.rholder.retry.StopStrategies中,它是一个策略工厂类。
publicinterfaceStopStrategy{/***Returns<code>true</code>iftheretryershouldstopretrying.**@paramfailedAttemptthepreviousfailed{@codeAttempt}*@return<code>true</code>iftheretryermuststop,<code>false</code>otherwise*/booleanshouldStop(AttemptfailedAttempt);}
4.1 NeverStopStrategy
此策略将永远重试,永不停止,查看其实现类,直接返回了false
@OverridepublicbooleanshouldStop(AttemptfailedAttempt){returnfalse;}
4.2 StopAfterAttemptStrategy
当执行次数到达指定次数之后停止重试,查看其实现类:
privatestaticfinalclassStopAfterAttemptStrategyimplementsStopStrategy{privatefinalintmaxAttemptNumber;publicStopAfterAttemptStrategy(intmaxAttemptNumber){Preconditions.checkArgument(maxAttemptNumber>=1,"maxAttemptNumbermustbe>=1butis%d",maxAttemptNumber);this.maxAttemptNumber=maxAttemptNumber;}@OverridepublicbooleanshouldStop(AttemptfailedAttempt){returnfailedAttempt.getAttemptNumber()>=maxAttemptNumber;}}
4.3 StopAfterDelayStrategy
当距离方法的第一次执行超出了指定的delay时间时停止,也就是说一直进行重试,当进行下一次重试的时候会判断从第一次执行到现在的所消耗的时间是否超过了这里指定的delay时间,查看其实现:
privatestaticfinalclassStopAfterAttemptStrategyimplementsStopStrategy{privatefinalintmaxAttemptNumber;publicStopAfterAttemptStrategy(intmaxAttemptNumber){Preconditions.checkArgument(maxAttemptNumber>=1,"maxAttemptNumbermustbe>=1butis%d",maxAttemptNumber);this.maxAttemptNumber=maxAttemptNumber;}@OverridepublicbooleanshouldStop(AttemptfailedAttempt){returnfailedAttempt.getAttemptNumber()>=maxAttemptNumber;}}
五.重试间隔策略WaitStrategy以及重试阻塞策略BlockStrategy
这两个策略放在一起说,它们合起来的作用就是用来控制重试任务之间的间隔时间,以及如何任务在等待时间间隔时如何阻塞。也就是说WaitStrategy决定了重试任务等待多久后进行下一次任务的执行,BlockStrategy用来决定任务如何等待。它们两的策略工厂分别为com.github.rholder.retry.WaitStrategies和BlockStrategies。
5.1 BlockStrategy
5.1.1 ThreadSleepStrategy
这个是BlockStrategies,决定如何阻塞任务,其主要就是通过Thread.sleep()来进行阻塞的,查看其实现:
@ImmutableprivatestaticclassThreadSleepStrategyimplementsBlockStrategy{@Overridepublicvoidblock(longsleepTime)throwsInterruptedException{Thread.sleep(sleepTime);}}
5.2 WaitStrategy
5.2.1 IncrementingWaitStrategy
该策略在决定任务间隔时间时,返回的是一个递增的间隔时间,即每次任务重试间隔时间逐步递增,越来越长,查看其实现:
privatestaticfinalclassIncrementingWaitStrategyimplementsWaitStrategy{privatefinallonginitialSleepTime;privatefinallongincrement;publicIncrementingWaitStrategy(longinitialSleepTime,longincrement){Preconditions.checkArgument(initialSleepTime>=0L,"initialSleepTimemustbe>=0butis%d",initialSleepTime);this.initialSleepTime=initialSleepTime;this.increment=increment;}@OverridepubliclongcomputeSleepTime(AttemptfailedAttempt){longresult=initialSleepTime+(increment*(failedAttempt.getAttemptNumber()-1));returnresult>=0L?result:0L;}}
该策略输入一个起始间隔时间值和一个递增步长,然后每次等待的时长都递增increment时长。
5.2.2 RandomWaitStrategy
顾名思义,返回一个随机的间隔时长,我们需要传入的就是一个最小间隔和最大间隔,然后随机返回介于两者之间的一个间隔时长,其实现为:
privatestaticfinalclassRandomWaitStrategyimplementsWaitStrategy{privatestaticfinalRandomRANDOM=newRandom();privatefinallongminimum;privatefinallongmaximum;publicRandomWaitStrategy(longminimum,longmaximum){Preconditions.checkArgument(minimum>=0,"minimummustbe>=0butis%d",minimum);Preconditions.checkArgument(maximum>minimum,"maximummustbe>minimumbutmaximumis%dandminimumis",maximum,minimum);this.minimum=minimum;this.maximum=maximum;}@OverridepubliclongcomputeSleepTime(AttemptfailedAttempt){longt=Math.abs(RANDOM.nextLong())%(maximum-minimum);returnt+minimum;}}
5.2.3 FixedWaitStrategy
该策略是返回一个固定时长的重试间隔。查看其实现:
privateintinvokeCount=0;publicintrealAction(intnum){invokeCount++;System.out.println(String.format("当前执行第%d次,num:%d",invokeCount,num));if(num<=0){thrownewIllegalArgumentException();}returnnum;}@TestpublicvoidguavaRetryTest001(){Retryer<Integer>retryer=RetryerBuilder.<Integer>newBuilder()//非正数进行重试.retryIfRuntimeException()//偶数则进行重试.retryIfResult(result->result%2==0)//设置最大执行次数3次.withStopStrategy(StopStrategies.stopAfterAttempt(3)).build();try{invokeCount=0;retryer.call(()->realAction(0));}catch(Exceptione){System.out.println("执行0,异常:"+e.getMessage());}try{invokeCount=0;retryer.call(()->realAction(1));}catch(Exceptione){System.out.println("执行1,异常:"+e.getMessage());}try{invokeCount=0;retryer.call(()->realAction(2));}catch(Exceptione){System.out.println("执行2,异常:"+e.getMessage());}}0
5.2.4 ExceptionWaitStrategy
该策略是由方法执行异常来决定是否重试任务之间进行间隔等待,以及间隔多久。
privateintinvokeCount=0;publicintrealAction(intnum){invokeCount++;System.out.println(String.format("当前执行第%d次,num:%d",invokeCount,num));if(num<=0){thrownewIllegalArgumentException();}returnnum;}@TestpublicvoidguavaRetryTest001(){Retryer<Integer>retryer=RetryerBuilder.<Integer>newBuilder()//非正数进行重试.retryIfRuntimeException()//偶数则进行重试.retryIfResult(result->result%2==0)//设置最大执行次数3次.withStopStrategy(StopStrategies.stopAfterAttempt(3)).build();try{invokeCount=0;retryer.call(()->realAction(0));}catch(Exceptione){System.out.println("执行0,异常:"+e.getMessage());}try{invokeCount=0;retryer.call(()->realAction(1));}catch(Exceptione){System.out.println("执行1,异常:"+e.getMessage());}try{invokeCount=0;retryer.call(()->realAction(2));}catch(Exceptione){System.out.println("执行2,异常:"+e.getMessage());}}1
5.2.5 CompositeWaitStrategy
这个没啥好说的,顾名思义,就是一个策略的组合,你可以传入多个WaitStrategy,然后所有WaitStrategy返回的间隔时长相加就是最终的间隔时间。查看其实现:
privateintinvokeCount=0;publicintrealAction(intnum){invokeCount++;System.out.println(String.format("当前执行第%d次,num:%d",invokeCount,num));if(num<=0){thrownewIllegalArgumentException();}returnnum;}@TestpublicvoidguavaRetryTest001(){Retryer<Integer>retryer=RetryerBuilder.<Integer>newBuilder()//非正数进行重试.retryIfRuntimeException()//偶数则进行重试.retryIfResult(result->result%2==0)//设置最大执行次数3次.withStopStrategy(StopStrategies.stopAfterAttempt(3)).build();try{invokeCount=0;retryer.call(()->realAction(0));}catch(Exceptione){System.out.println("执行0,异常:"+e.getMessage());}try{invokeCount=0;retryer.call(()->realAction(1));}catch(Exceptione){System.out.println("执行1,异常:"+e.getMessage());}try{invokeCount=0;retryer.call(()->realAction(2));}catch(Exceptione){System.out.println("执行2,异常:"+e.getMessage());}}2
5.2.6 FibonacciWaitStrategy
这个策略与IncrementingWaitStrategy有点相似,间隔时间都是随着重试次数的增加而递增的,不同的是,FibonacciWaitStrategy是按照斐波那契数列来进行计算的,使用这个策略时,我们需要传入一个乘数因子和最大间隔时长,其实现就不贴了
5.2.7 ExponentialWaitStrategy
这个与IncrementingWaitStrategy、FibonacciWaitStrategy也类似,间隔时间都是随着重试次数的增加而递增的,但是该策略的递增是呈指数级递增。查看其实现:
privateintinvokeCount=0;publicintrealAction(intnum){invokeCount++;System.out.println(String.format("当前执行第%d次,num:%d",invokeCount,num));if(num<=0){thrownewIllegalArgumentException();}returnnum;}@TestpublicvoidguavaRetryTest001(){Retryer<Integer>retryer=RetryerBuilder.<Integer>newBuilder()//非正数进行重试.retryIfRuntimeException()//偶数则进行重试.retryIfResult(result->result%2==0)//设置最大执行次数3次.withStopStrategy(StopStrategies.stopAfterAttempt(3)).build();try{invokeCount=0;retryer.call(()->realAction(0));}catch(Exceptione){System.out.println("执行0,异常:"+e.getMessage());}try{invokeCount=0;retryer.call(()->realAction(1));}catch(Exceptione){System.out.println("执行1,异常:"+e.getMessage());}try{invokeCount=0;retryer.call(()->realAction(2));}catch(Exceptione){System.out.println("执行2,异常:"+e.getMessage());}}3
六.重试监听器RetryListener
当发生重试时,将会调用RetryListener的onRetry方法,此时我们可以进行比如记录日志等额外操作。
privateintinvokeCount=0;publicintrealAction(intnum){invokeCount++;System.out.println(String.format("当前执行第%d次,num:%d",invokeCount,num));if(num<=0){thrownewIllegalArgumentException();}returnnum;}@TestpublicvoidguavaRetryTest001(){Retryer<Integer>retryer=RetryerBuilder.<Integer>newBuilder()//非正数进行重试.retryIfRuntimeException()//偶数则进行重试.retryIfResult(result->result%2==0)//设置最大执行次数3次.withStopStrategy(StopStrategies.stopAfterAttempt(3)).build();try{invokeCount=0;retryer.call(()->realAction(0));}catch(Exceptione){System.out.println("执行0,异常:"+e.getMessage());}try{invokeCount=0;retryer.call(()->realAction(1));}catch(Exceptione){System.out.println("执行1,异常:"+e.getMessage());}try{invokeCount=0;retryer.call(()->realAction(2));}catch(Exceptione){System.out.println("执行2,异常:"+e.getMessage());}}4
输出:
privateintinvokeCount=0;publicintrealAction(intnum){invokeCount++;System.out.println(String.format("当前执行第%d次,num:%d",invokeCount,num));if(num<=0){thrownewIllegalArgumentException();}returnnum;}@TestpublicvoidguavaRetryTest001(){Retryer<Integer>retryer=RetryerBuilder.<Integer>newBuilder()//非正数进行重试.retryIfRuntimeException()//偶数则进行重试.retryIfResult(result->result%2==0)//设置最大执行次数3次.withStopStrategy(StopStrategies.stopAfterAttempt(3)).build();try{invokeCount=0;retryer.call(()->realAction(0));}catch(Exceptione){System.out.println("执行0,异常:"+e.getMessage());}try{invokeCount=0;retryer.call(()->realAction(1));}catch(Exceptione){System.out.println("执行1,异常:"+e.getMessage());}try{invokeCount=0;retryer.call(()->realAction(2));}catch(Exceptione){System.out.println("执行2,异常:"+e.getMessage());}}5
七.重试原理
其实到这一步之后,实现原理大概就很清楚了,就是由上述各种策略配合从而达到了非常灵活的重试机制。在这之前我们看一个上面没说的东东-Attempt
privateintinvokeCount=0;publicintrealAction(intnum){invokeCount++;System.out.println(String.format("当前执行第%d次,num:%d",invokeCount,num));if(num<=0){thrownewIllegalArgumentException();}returnnum;}@TestpublicvoidguavaRetryTest001(){Retryer<Integer>retryer=RetryerBuilder.<Integer>newBuilder()//非正数进行重试.retryIfRuntimeException()//偶数则进行重试.retryIfResult(result->result%2==0)//设置最大执行次数3次.withStopStrategy(StopStrategies.stopAfterAttempt(3)).build();try{invokeCount=0;retryer.call(()->realAction(0));}catch(Exceptione){System.out.println("执行0,异常:"+e.getMessage());}try{invokeCount=0;retryer.call(()->realAction(1));}catch(Exceptione){System.out.println("执行1,异常:"+e.getMessage());}try{invokeCount=0;retryer.call(()->realAction(2));}catch(Exceptione){System.out.println("执行2,异常:"+e.getMessage());}}6
通过接口方法可以知道Attempt这个类包含了任务执行次数、任务执行异常、任务执行结果、以及首次执行任务至今的时间间隔,那么我们后续的不管重试时机、还是其他策略都是根据此值来决定。
接下来看关键执行入口Retryer#call:
privateintinvokeCount=0;publicintrealAction(intnum){invokeCount++;System.out.println(String.format("当前执行第%d次,num:%d",invokeCount,num));if(num<=0){thrownewIllegalArgumentException();}returnnum;}@TestpublicvoidguavaRetryTest001(){Retryer<Integer>retryer=RetryerBuilder.<Integer>newBuilder()//非正数进行重试.retryIfRuntimeException()//偶数则进行重试.retryIfResult(result->result%2==0)//设置最大执行次数3次.withStopStrategy(StopStrategies.stopAfterAttempt(3)).build();try{invokeCount=0;retryer.call(()->realAction(0));}catch(Exceptione){System.out.println("执行0,异常:"+e.getMessage());}try{invokeCount=0;retryer.call(()->realAction(1));}catch(Exceptione){System.out.println("执行1,异常:"+e.getMessage());}try{invokeCount=0;retryer.call(()->realAction(2));}catch(Exceptione){System.out.println("执行2,异常:"+e.getMessage());}}7
八.总结
通篇下来可以看到其实核心实现并不难,但是此框架通过建造者模式和策略模式组合运用,提供了十分清晰明了且灵活的重试机制,其设计思路还是值得借鉴学习!