当前位置: 首页 > 科技观察

RetryRetry组件非常简洁,使用起来也非常简单

时间:2023-03-16 02:00:39 科技观察

前言你是不是经常遇到接口调用异常和超时的场景?特别是在网络抖动导致超时的场景下,我们一般的产品都会要求我们重试几次。很多小伙伴的实现方式是写一个循环调用;对于(inti=1;i<=3;i++){try{if(doExec()){break;简单,但非常不灵活,不能针对很多场景。今天老谷给大家带来了一个非常流行的retry重试组件,就是guava-retrying组件。简单而强大的功能,是旅行必备的工具。依赖引用com.github.rholderguava-retrying2.0.0guava-retrying包中的应用有相关的guava版本依赖,如果和自己的项目有冲突,可以排除。示例执行方法@ServicepublicclassRetryService{privatestaticLoggerlogger=LoggerFactory.getLogger(RetryService.class);私人原子整数计数=新原子整数(0);publicintdoExec(){logger.info("调用了{}次",count.incrementAndGet());if(count.get()%2==0){thrownewException("----->异常");}返回count.get();}}里面定义了doExec方法,每次调用count加1,如果是2的倍数则抛出异常。调用方法publicStringtest01(){Retryerretryer=RetryerBuilder.newBuilder().retryIfRuntimeException()//retryIfResult表达式返回true,则重试.retryIfResult(result->{if(result%3==0){logger.info("----->shouldretry");returntrue;}returnfalse;}).withStopStrategy(StopStrategies.stopAfterAttempt(3)).build();尝试{重试器。调用(()->retryService.doExec());}catch(ExecutionExceptione){logger.error("异常1:{}",e.getMessage());}catch(RetryExceptione){logger.error("Exception:{}",e.getMessage());}return"ok";}从上面的代码,我们可以实现条件重试。Guava的重试思路分为重试条件、停止重试策略、重试间隔策略1、重试条件表示在什么情况下进行重试。重试组件中RetryerBuilder的retryIfXXX()方法用于设置重试的情况。一般分为两种:根据执行异常重试和根据方法执行结果重试。根据异常重试1.retryIfException()当方法执行抛出异常时重试Exception2.retryIfRuntimeException()当方法执行抛出异常RuntimeException时重试3.retryIfExceptionOfType(exceptionClass)当方法执行抛出异常时重试具体是哪个异常Retry4.retryIfException(Predicatep)自定义异常在什么情况下重试根据返回结果重试retryIfResult(@NonnullPredicateresultPredicate)根据返回值判断是否重试。//当返回true时,retry.retryIfResult(result->{if(result%3==0){logger.info("----->shouldberetried");returntrue;}returnfalse;})以上结果代表返回值,判断返回值大于3,返回true时重试。2.停止重试策略重试组件需要提供一个策略withStopStrategy来停止重试。最简单的方法是重试几次。1、StopAfterAttemptStrategy字面意思就知道了,就是执行次数达到指定次数后停止重试。.withStopStrategy(StopStrategies.stopAfterAttempt(3))2.NeverStopStrategy这个策略会一直重试,一直重试。.withStopStrategy(StopStrategies.neverStop())3.StopAfterDelayStrategy设置最长允许执行时间;例如设置最长执行时间10s,不管任务执行次数多少,只要重试与第一次执行的时间差超过最大时间,任务就会终止,并返回重试异常RetryException.withStopStrategy(StopStrategies.stopAfterDelay(10,TimeUnit.SECONDS))3.重试间隔策略在重试场景中,我们最好有一个重试间隔。如果没有间隔,很可能连续重试都会失败。WaitStrategy1,FixedWaitStrategy固定时间重试间隔。.withWaitStrategy(WaitStrategies.fixedWait(1,TimeUnit.SECONDS))表示重试间隔为1秒。2、RandomWaitStrategy随机间隔时长.withWaitStrategy(WaitStrategies.randomWait(1,TimeUnit.SECONDS,5,TimeUnit.SECONDS))第一个参数为最小间隔时长,第二个参数为最大间隔时长;两者之间取一个随机的持续时间。3、IncrementingWaitStrategy的递增间隔,即每个任务重试的间隔逐渐增加,越来越长。值和一个增量步骤,然后每次等待时间都增加增量时间。4.ExceptionWaitStrategy根据不同的异常决定不同的时间间隔。.withWaitStrategy(WaitStrategies.exceptionWait(Exception.class,newFunction(){@Overridepublic@NullableLongapply(@NullableExceptioninput){if(inputinstanceofNullPointerException){return1*1000L;}elseif(inputinstanceofIndexOutOfBoundsException){return2*1000L;}elseif(inputinstanceofIllegalStateException){return3*1000L;}return0L;}}))以上代码一目了然。以上是常用的等待策略,还有几个不太常用的等待策略,大家可以自行查看。至此,我们感觉少了一个很重要的模块;也就是说,我们可以知道任务是否已经重试了吗?或者我们需要记录重试的次数,或者在重试的时候,做一个错误日志告警,帮助我们关注系统的稳定性。下面介绍重试监听器。当重试监听器RetryListener发送重试时,它会调用RetryListener的onRetry方法,这样我们就可以做一些自定义的重试附加任务。定义一个继承RetryListener接口的类publicclassMyRetryListenerimplementsRetryListener{privatestaticLoggerlogger=LoggerFactory.getLogger(MyRetryListener.class);@OverridepublicvoidonRetry(Attemptattempt){if(attempt.hasResult()){logger.info("===>方法返回的结果:{}",attempt.getResult());}if(attempt.hasException()){logger.info("===>{}thtimeExecution,exception:{}",attempt.getAttemptNumber(),attempt.getExceptionCause()==null?"":attempt.getExceptionCause().getMessage());返回;}logger.info("===>{}执行",attempt.getAttemptNumber());}}添加RetryerBuilder;.withRetryListener(newMyRetryListener())这样就实现了监听业务重试的原理。guava-retrying这个组件功能比较强大,我们可以看看核心代码publicVcall(Callablecallable)throwsExecutionException,RetryException{longstartTime=System.nanoTime();//执行次数从1开始for(intattemptNumber=1;;attemptNumber++){Attempt;试图;try{//尝试执行Vresult=attemptTimeLimiter.call(callable);//如果执行成功,结果会被封装为ResultAttemptattempt=newRetryer.ResultAttempt(result,attemptNumber,TimeUnit.NANOSECONDS.toMillis(System.nanoTime()-startTime));}catch(Throwablet){//如果异常被执行,结果会被封装为ExceptionAttemptattempt=newRetryer.ExceptionAttempt(t,attemptNumber,TimeUnit.NANOSECONDS.toMillis(System.nanoTime()-startTime));}//在这里,执行结果被传递给RetryListener做一些额外的事情for(RetryListenerlistener:listeners){listener.onRetry(attempt);}//这里是决定是否重试的地方,如果不重试直接返回结果,执行成功则返回结果,执行失败则返回异常此时先判断是否到了停止的时间重试,如果是则直接返回异常}else{//确定重试间隔longsleepTime=waitStrategy.计算睡眠时间(尝试);尝试{//进行阻塞blockStrategy.block(sleepTime);}catch(InterruptedExceptione){Thread.currentThread().interrupt();抛出新的RetryException(attemptNumber,尝试);}}}}