非常非常地重试重试组件,使用杠铃的 不念不忘少年蓝@ 2024-04-06 11:01 23阅读 0赞 **前言** 小伙伴是不是经常遇到接口调用异常的情况? 很多小伙伴的实现方式是写个循环调用; <span style="color:#444444"><span style="background-color:#f6f6f6"><span style="color:#333333"><strong>for</strong></span>(<span style="color:#397300">int</span> i= <span style="color:#880000">1</span> ;i<= <span style="color:#880000">3</span> ;i++){ <span style="color:#333333"><strong>try</strong></span> { <span style="color:#333333"><strong>if</strong></span> (doExec()){ <span style="color:#333333"><strong>break</strong></span> ; } }<span style="color:#333333"><strong>抓</strong></span>{ } }</span></span> 方式是比较简单的,但是非常不灵活,今天出门不能很顾虑,但是这种情况很顾虑。实现给大家带来一个重试重试的组件,流行度很明亮,即是番石榴重试组件。功能强大,是平日旅行的必备工具。 **引用** <span style="color:#444444"><span style="background-color:#f6f6f6">< <span style="color:#333333"><strong>dependency</strong></span> > < <span style="color:#333333"><strong>groupId</strong></span> > com.github.rholder </ <span style="color:#333333"><strong>groupId</strong></span> > < <span style="color:#333333"><strong>artifactId</strong></span> > guava- retrying< / <span style="color:#333333"><strong>artifactId</strong></span> > < <span style="color:#333333"><strong>version</strong></span> > 2.0.0 </ <span style="color:#333333"><strong>version</strong></span> > <span style="color:#888888"><!--修复与guava重复的依赖--> </span> <span style="color:#888888"><! -- <exclusions>--> </span> <span style="color:#888888"><!-- <exclusion>--> </span> <span style="color:#888888"><!-- <groupId>com.google.guava</groupId>--> </span> <span style="color:#888888"><!-- <artifactId>guava</artifactId>- -> </span> <span style="color:#888888"><!-- </exclusion>--> </span> <span style="color:#888888"><!-- <排除>-></span> <span style="color:#888888"><!-- <groupId>com.google.code.findbugs</groupId>--> </span> <span style="color:#888888"><!-- <artifactId>jsr305</artifactId>--> </span> <span style="color:#888888"><!-- </exclusion>--> </span> <span style="color:#888888"><!- - </exclusions>--> </span> </<span style="color:#333333"><strong>依赖</strong></span>></span></span> guava-retrying包中应用有相关的guava版本依赖,如果和自身项目突破可以解决。 **示例** **执行方法** <span style="color:#444444"><span style="background-color:#f6f6f6"><span style="color:#880000"><strong>@Service</strong></span><span style="color:#333333"><strong> public </strong></span><span style="color:#333333"><strong>class</strong></span> RetryService { <span style="color:#333333"><strong>private </strong></span><span style="color:#333333"><strong>static </strong></span><span style="color:#880000">Logger </span><span style="color:#333333"><strong>logger</strong></span> = <span style="color:#880000">LoggerFactory.getLogger</span> ( <span style="color:#880000">RetryService.class</span> ) ; <span style="color:#880000">_ </span> <span style="color:#333333"><strong>私有</strong></span><span style="color:#880000">AtomicInteger</span><span style="color:#397300">计数</span>= 新<span style="color:#880000">AtomicInteger</span> ( <span style="color:#880000">0</span> ); <span style="color:#333333"><strong>公共</strong></span>int doExec(){ logger.info( <span style="color:#880000">"调用了{}次"</span> , <span style="color:#397300">count</span> .incrementAndGet()); <span style="color:#333333"><strong>if</strong></span> ( <span style="color:#397300">count</span> . <span style="color:#333333"><strong>get</strong></span> () % <span style="color:#880000">2</span> == <span style="color:#880000">0</span> ){ <span style="color:#333333"><strong>throw</strong></span> new <span style="color:#880000">Exception</span> ( <span style="color:#880000">"----->异常了哦"</span> ); } <span style="color:#333333"><strong>返回</strong></span> <span style="color:#397300">计数</span>。<span style="color:#333333"><strong>得到</strong></span>(); } }</span></span> 其中定义了doExec方法,每次调用计数加1,如果是的倍数就抛。 **调用方法** <span style="color:#444444"><span style="background-color:#f6f6f6"><span style="color:#333333"><strong>公共</strong></span>字符串 test01(){ Retryer<Integer> retryer = RetryerBuilder.<Integer>newBuilder() .retryIfRuntimeException() <span style="color:#888888">//retryIfResult 表达式返回true,则重试</span> .retryIfResult(结果 -> { <span style="color:#333333"><strong>如果</strong></span>(结果 % <span style="color:#880000">3</span> == <span style="color:#880000">0</span>){ logger.info( <span style="color:#880000">"----->应该试重了"</span> ); <span style="color:#333333"><strong>返回</strong></span> <span style="color:#78a960">真</span>; } <span style="color:#333333"><strong>返回</strong></span> <span style="color:#78a960">假</span>; }) .withStopStrategy(StopStrategies.stopAfterAttempt( <span style="color:#880000">3</span> )) 。建造(); <span style="color:#333333"><strong>试试</strong></span>{ retryer.call(() -> retryService.doExec()); }<span style="color:#333333"><strong>捕捉</strong></span>(ExecutionException e){ logger.error( <span style="color:#880000">"异常1:{}"</span> ,e.getMessage()); }<span style="color:#333333"><strong>捕捉</strong></span>(重试异常 e){ logger.error( <span style="color:#880000">"异常:{}"</span> ,e.getMessage()); } <span style="color:#333333"><strong>返回</strong></span> <span style="color:#880000">“确定”</span>; }</span></span> 从上面代码中,我们就可以实现条件重试。 番石榴的重试思想可分为重试重条件、停试策略、重试间歇策略 #### **一、试重条件** #### 表示在什么情况下,重试。重试组件中的RetryerBuilder的retryIfXXX()方法使用设置在什么情况下进行重试,用户可以在什么情况下根据执行异常进行重试和根据方法执行结果进行重试三个。 **可知异常重试** 1、retryIfException() 当执行方法抛出异常时重试 2、retryIfRuntimeException()当方法执行抛出异常RuntimeException时重试 3、retryIfExceptionOfType(exceptionClass)当方法执行抛出异常具体哪个异常时重试 4、retryIfException(Predicate p)自定义异常什么情况下重试 **可知返回结果重试** retryIfResult(@Nonnull Predicate<V> resultPredicate)根据返回值判断是否重试。 <span style="color:#444444"><span style="background-color:#f6f6f6"><span style="color:#888888">//返回真时,重试</span> .retryIfResult(结果 -> { <span style="color:#333333"><strong>如果</strong></span>(结果 % <span style="color:#880000">3</span> == <span style="color:#880000">0</span>){ logger.info( <span style="color:#880000">"----->应该试重了"</span> ); <span style="color:#333333"><strong>返回</strong></span> <span style="color:#78a960">真</span>; } <span style="color:#333333"><strong>返回</strong></span> <span style="color:#78a960">假</span>; })</span></span> 上面的结果代表的是返回值,判断返回值对3取余,返回真实时则进行重试。 #### **二、停止重试策略** #### 重试需要提供停止重试的策略withStopStrategy,简单的方式就是重试次数最多 **1、StopAfterAttempt策略** 从面字上面就知道什么英文,即在执行次数指定次数之后停止重试。 <span style="color:#444444"><span style="background-color:#f6f6f6"><span style="color:#880000">.withStopStrategy</span> ( <span style="color:#333333"><strong>StopStrategies </strong></span><span style="color:#880000">.stopAfterAttempt</span> (3))</span></span> **2、永不停止策略** 永远重试,一直重试 <span style="color:#444444"><span style="background-color:#f6f6f6"><span style="color:#880000">.withStopStrategy</span> ( <span style="color:#333333"><strong>StopStrategies </strong></span><span style="color:#880000">.neverStop</span> ())</span></span> **3、StopAfterDelay策略** 设定一个最长的时间;只要设定一次执行最多执行10s,不计次数,试试的时间与第一次的时间差,最长时间,执行则执行,返回执行重试重试异常 <span style="color:#444444"><span style="background-color:#f6f6f6"><span style="color:#880000">.withStopStrategy</span> ( <span style="color:#333333"><strong>StopStrategies </strong></span><span style="color:#880000">.stopAfterDelay</span> (10, <span style="color:#333333"><strong>TimeUnit </strong></span><span style="color:#880000">.SECONDS</span> ))</span></span> #### **三、重试间隔策略** #### 在重试场景中,我们最好的试试重试的间隔,如果没有间隔,可能有连续的重试失败。 **等待策略** **1、固定等待策略** 固定时长重试间歇。 <span style="color:#444444"><span style="background-color:#f6f6f6"><span style="color:#880000">.withWaitStrategy</span> ( <span style="color:#333333"><strong>WaitStrategies </strong></span><span style="color:#880000">.fixedWait</span> (1, <span style="color:#333333"><strong>TimeUnit </strong></span><span style="color:#880000">.SECONDS</span> ))</span></span> 那就是重试间隔为1秒。 **2、随机等待策略** 随时的间隔时长 <span style="color:#444444"><span style="background-color:#f6f6f6"><span style="color:#880000">.withWaitStrategy</span> ( <span style="color:#333333"><strong>WaitStrategies </strong></span><span style="color:#880000">.randomWait</span> (1, <span style="color:#333333"><strong>TimeUnit </strong></span><span style="color:#880000">.SECONDS</span> ,5, <span style="color:#333333"><strong>TimeUnit </strong></span><span style="color:#880000">.SECONDS</span> ))</span></span> 第1个间隔时间长,最小个间隔时间长的时间;每次间隔时间是第二个参数。 **3、增量等待策略** 递增的间隔时间长,即每次任务重试的时间递增,越来越长 <span style="color:#444444"><span style="background-color:#f6f6f6"><span style="color:#880000">.withWaitStrategy</span> ( <span style="color:#333333"><strong>WaitStrategies </strong></span><span style="color:#880000">.incrementingWait</span> (3, <span style="color:#333333"><strong>TimeUnit </strong></span><span style="color:#880000">.SECONDS</span> ,1, <span style="color:#333333"><strong>TimeUnit </strong></span><span style="color:#880000">.SECONDS</span> ))</span></span> 该策略输入一个每隔一段时间的等待时间增加一个步长,然后每次值和时间的长都递增。 **4、异常等待策略** 根据不同的不同,决定不同的间隔时长。 <span style="color:#444444"><span style="background-color:#f6f6f6"><span style="color:#880000">.withWaitStrategy</span> (WaitStrategies.exceptionWait(Exception.class, new Function<Exception, Long>() { <span style="color:#bc6060">@Override</span> public <span style="color:#bc6060">@Nullable</span> Long apply( <span style="color:#bc6060">@Nullable</span>异常输入) { if (input instanceof NullPointerException){ 返回 <span style="color:#880000">1</span> * <span style="color:#880000">1000</span>升; }else if (input instanceof IndexOutOfBoundsException){ 返回 <span style="color:#880000">2</span> * <span style="color:#880000">1000</span>升; }else if (input instanceof IllegalStateException){ 返回 <span style="color:#880000">3</span> * <span style="color:#880000">1000</span>升; } 返回<span style="color:#880000">0</span>升; } }))</span></span> 这个的代码一看就知道了。 是一些常见的策略,不常用的策略,小伙伴们先等待。 我们需要记录一个非常重要的试试机会关注系统的产品。 我们来介绍一下重试监听器。 **重试监听器RetryListener** 当重试时,会调用RetryListener的onRetry方法,这样我们就可以做一些自定义的重试的额外任务。 **定义一个类,继承RetryListener接口** <span style="color:#444444"><span style="background-color:#f6f6f6"><span style="color:#333333"><strong>公共</strong></span> <span style="color:#333333"><strong>类</strong></span> <span style="color:#880000"><strong>MyRetryListener</strong></span> <span style="color:#333333"><strong>实现</strong></span> <span style="color:#880000"><strong>RetryListener</strong></span> { <span style="color:#333333"><strong>私有</strong></span> <span style="color:#333333"><strong>静态</strong></span>Logger logger = LoggerFactory.getLogger(MyRetryListener.class); <span style="color:#1f7199">@Override </span> <span style="color:#333333"><strong>public</strong></span> <Integer> <span style="color:#333333"><strong>void </strong></span> <span style="color:#880000"><strong>onRetry </strong></span>(Attempt<Integer> 尝试) { <span style="color:#333333"><strong>if</strong></span> (attempt.hasResult()){ logger.info( <span style="color:#880000">"===> 方法返回的结果:{}"</span> ,attempt.getResult()); } <span style="color:#333333"><strong>if</strong></span> (attempt.hasException()){ logger.info( <span style="color:#880000">"===>第{}次执行, 异常:{}"</span> ,attempt.getAttemptNumber(),attempt.getExceptionCause()== <span style="color:#333333"><strong>null</strong></span> ? <span style="color:#880000">""</span> : attempt.getExceptionCause().getMessage()); <span style="color:#333333"><strong>返回</strong></span>; } logger.info( <span style="color:#880000">"===>第{}次执行"</span> ,attempt.getAttemptNumber()); } }</span></span> 在RetryerBuilder中加入; <span style="color:#444444"><span style="background-color:#f6f6f6"><span style="color:#880000">.withRetryListener</span> (new MyRetryListener())</span></span> 这样就实现了监听业务 **重试原理** guava-retrying的组件功能还是比较强大的,我们可以看看内核的代码 <span style="color:#444444"><span style="background-color:#f6f6f6"><span style="color:#333333"><strong>public</strong></span> V <span style="color:#333333"><strong>call</strong></span> (Callable<V> callable) <span style="color:#333333"><strong>throws</strong></span> ExecutionException, RetryException { <span style="color:#333333"><strong>long</strong></span> startTime = System.nanoTime(); <span style="color:#888888">// 执行次数从</span> <span style="color:#333333"><strong>1Startfor</strong></span> ( <span style="color:#333333"><strong>int</strong></span> attemptNumber = <span style="color:#880000">1</span> ; ; attemptNumber++) { 尝试<V>尝试; <span style="color:#333333"><strong>try</strong></span> { <span style="color:#888888">//</span>尝试执行 V result = attemptTimeLimiter. <span style="color:#333333"><strong>调用</strong></span>(可调用); <span style="color:#888888">// 执行成功则将结果封装为ResultAttempt</span> attempt = <span style="color:#333333"><strong>Retryer.ResultAttempt</strong></span> <V>(result, tryNumberlis(System.nanoTime() - startTime)); } <span style="color:#333333"><strong>catch</strong></span> ( <span style="color:#888888">Thrable t) { // 执行异常则将封装</span><span style="color:#333333"><strong>结果</strong></span>为ExceptionAttempt } <span style="color:#888888">// 这里将执行结果传给RetryListener 做一些额外的事情</span> <span style="color:#333333"><strong>for</strong></span> (RetryListener listener : listeners) { listener.onRetry(尝试); } <span style="color:#888888">//这个就是决定是否要进行重试的地方,如果不进行重试直接返回结果,执行成功就返回结果,执行失败就返回异常</span> <span style="color:#333333"><strong>if</strong></span> (!rejectionPredicate.apply(attempt)) { <span style="color:#333333"><strong>return</strong></span> attempt.get() ; } <span style="color:#888888">// 到这里,说明需要重试,此时先决定是否达到异常则停止重试的时机,如果了则直接返回</span> <span style="color:#333333"><strong>if</strong></span> (stopStrategy.shouldStop(attempt)) { <span style="color:#333333"><strong>throw </strong></span> <span style="color:#333333"><strong>new</strong></span> RetryException(attemptNumber, attempt) ; } <span style="color:#333333"><strong>else</strong></span> { <span style="color:#888888">// 决定重试间隔</span> <span style="color:#333333"><strong>long</strong></span> sleepTime = waitStrategy.computeSleepTime(attempt); <span style="color:#333333"><strong>尝试</strong></span>{ <span style="color:#888888">// 进行阻尼</span> blockStrategy.block(sleepTime); }<span style="color:#333333"><strong>捕捉</strong></span>(InterruptedException e){ Thread.currentThread().interrupt(); <span style="color:#333333"><strong>抛出</strong></span> <span style="color:#333333"><strong>新</strong></span>的重试异常(尝试编号,尝试); } } } }</span></span>
还没有评论,来说两句吧...