<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring-Retry 和 Guava-Retry,各有千秋

          共 17416字,需瀏覽 35分鐘

           ·

          2022-09-18 00:59

          點(diǎn)擊下方“IT牧場”,選擇“設(shè)為星標(biāo)”

          一 重試框架之Spring-Retry

          Spring Retry 為 Spring 應(yīng)用程序提供了聲明性重試支持。它用于Spring批處理、Spring集成、Apache Hadoop(等等)。它主要是針對(duì)可能拋出異常的一些調(diào)用操作,進(jìn)行有策略的重試

          1. Spring-Retry的普通使用方式

          1.準(zhǔn)備工作

          我們只需要加上依賴:

           <dependency>
              <groupId>org.springframework.retry</groupId>
              <artifactId>spring-retry</artifactId>
              <version>1.2.2.RELEASE</version>
           </dependency>

          準(zhǔn)備一個(gè)任務(wù)方法,我這里是采用一個(gè)隨機(jī)整數(shù),根據(jù)不同的條件返回不同的值,或者拋出異常

          package com.zgd.demo.thread.retry;

          import lombok.extern.slf4j.Slf4j;
          import org.apache.commons.lang3.RandomUtils;
          import org.springframework.remoting.RemoteAccessException;

          /**
           * @Author: zgd
           * @Description:
           */

          @Slf4j
          public class RetryDemoTask {


            /**
             * 重試方法
             * @return
             */

            public static boolean retryTask(String param)  {
              log.info("收到請(qǐng)求參數(shù):{}",param);

              int i = RandomUtils.nextInt(0,11);
              log.info("隨機(jī)生成的數(shù):{}",i);
              if (i == 0) {
                log.info("為0,拋出參數(shù)異常.");
                throw new IllegalArgumentException("參數(shù)異常");
              }else if (i  == 1){
                log.info("為1,返回true.");
                return true;
              }else if (i == 2){
                log.info("為2,返回false.");
                return false;
              }else{
                //為其他
                  log.info("大于2,拋出自定義異常.");
                  throw new RemoteAccessException("大于2,拋出遠(yuǎn)程訪問異常");
                }
              }

          }
          2.使用SpringRetryTemplate

          這里可以寫我們的代碼了

          package com.zgd.demo.thread.retry.spring;

          import com.zgd.demo.thread.retry.RetryDemoTask;
          import lombok.extern.slf4j.Slf4j;
          import org.junit.Test;
          import org.springframework.remoting.RemoteAccessException;
          import org.springframework.retry.backoff.FixedBackOffPolicy;
          import org.springframework.retry.policy.SimpleRetryPolicy;
          import org.springframework.retry.support.RetryTemplate;

          import java.util.HashMap;
          import java.util.Map;

          /**
           * @Author: zgd
           * @Description: spring-retry 重試框架
           */

          @Slf4j
          public class SpringRetryTemplateTest {

            /**
             * 重試間隔時(shí)間ms,默認(rèn)1000ms
             * */

            private long fixedPeriodTime = 1000L;
            /**
             * 最大重試次數(shù),默認(rèn)為3
             */

            private int maxRetryTimes = 3;
            /**
             * 表示哪些異常需要重試,key表示異常的字節(jié)碼,value為true表示需要重試
             */

            private Map<Class<? extends Throwable>, Boolean> exceptionMap = new HashMap<>();


            @Test
            public void test() {
              exceptionMap.put(RemoteAccessException.class,true);

              // 構(gòu)建重試模板實(shí)例
              RetryTemplate retryTemplate = new RetryTemplate();

              // 設(shè)置重試回退操作策略,主要設(shè)置重試間隔時(shí)間
              FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
              backOffPolicy.setBackOffPeriod(fixedPeriodTime);

              // 設(shè)置重試策略,主要設(shè)置重試次數(shù)
              SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(maxRetryTimes, exceptionMap);

              retryTemplate.setRetryPolicy(retryPolicy);
              retryTemplate.setBackOffPolicy(backOffPolicy);

              Boolean execute = retryTemplate.execute(
                      //RetryCallback
                      retryContext -> {
                        boolean b = RetryDemoTask.retryTask("abc");
                        log.info("調(diào)用的結(jié)果:{}", b);
                        return b;
                      },
                      retryContext -> {
                        //RecoveryCallback
                        log.info("已達(dá)到最大重試次數(shù)或拋出了不重試的異常~~~");
                        return false;
                      }
                );

              log.info("執(zhí)行結(jié)果:{}",execute);

            }

          }

          簡單剖析下案例代碼,RetryTemplate 承擔(dān)了重試執(zhí)行者的角色,它可以設(shè)置SimpleRetryPolicy(重試策略,設(shè)置重試上限,重試的根源實(shí)體),FixedBackOffPolicy(固定的回退策略,設(shè)置執(zhí)行重試回退的時(shí)間間隔)。

          RetryTemplate通過execute提交執(zhí)行操作,需要準(zhǔn)備RetryCallback 和RecoveryCallback 兩個(gè)類實(shí)例,前者對(duì)應(yīng)的就是重試回調(diào)邏輯實(shí)例,包裝正常的功能操作,RecoveryCallback實(shí)現(xiàn)的是整個(gè)執(zhí)行操作結(jié)束的恢復(fù)操作實(shí)例.

          只有在調(diào)用的時(shí)候拋出了異常,并且異常是在exceptionMap中配置的異常,才會(huì)執(zhí)行重試操作,否則就調(diào)用到excute方法的第二個(gè)執(zhí)行方法RecoveryCallback

          當(dāng)然,重試策略還有很多種,回退策略也是:

          重試策略

          • NeverRetryPolicy: 只允許調(diào)用RetryCallback一次,不允許重試

          • AlwaysRetryPolicy: 允許無限重試,直到成功,此方式邏輯不當(dāng)會(huì)導(dǎo)致死循環(huán)

          • SimpleRetryPolicy: 固定次數(shù)重試策略,默認(rèn)重試最大次數(shù)為3次,RetryTemplate默認(rèn)使用的策略

          • TimeoutRetryPolicy: 超時(shí)時(shí)間重試策略,默認(rèn)超時(shí)時(shí)間為1秒,在指定的超時(shí)時(shí)間內(nèi)允許重試

          • ExceptionClassifierRetryPolicy: 設(shè)置不同異常的重試策略,類似組合重試策略,區(qū)別在于這里只區(qū)分不同異常的重試

          • CircuitBreakerRetryPolicy: 有熔斷功能的重試策略,需設(shè)置3個(gè)參數(shù)openTimeout、resetTimeoutdelegate

          • CompositeRetryPolicy: 組合重試策略,有兩種組合方式,樂觀組合重試策略是指只要有一個(gè)策略允許即可以重試,悲觀組合重試策略是指只要有一個(gè)策略不允許即可以重試,但不管哪種組合方式,組合中的每一個(gè)策略都會(huì)執(zhí)行

          重試回退策略

          重試回退策略,指的是每次重試是立即重試還是等待一段時(shí)間后重試。

          默認(rèn)情況下是立即重試,如果需要配置等待一段時(shí)間后重試則需要指定回退策略BackoffRetryPolicy。

          • NoBackOffPolicy: 無退避算法策略,每次重試時(shí)立即重試

          • FixedBackOffPolicy: 固定時(shí)間的退避策略,需設(shè)置參數(shù)sleeperbackOffPeriod,sleeper指定等待策略,默認(rèn)是Thread.sleep,即線程休眠,backOffPeriod指定休眠時(shí)間,默認(rèn)1秒

          • UniformRandomBackOffPolicy: 隨機(jī)時(shí)間退避策略,需設(shè)置sleeper、minBackOffPeriodmaxBackOffPeriod,該策略在minBackOffPeriod,maxBackOffPeriod之間取一個(gè)隨機(jī)休眠時(shí)間,minBackOffPeriod默認(rèn)500毫秒,maxBackOffPeriod默認(rèn)1500毫秒

          • ExponentialBackOffPolicy: 指數(shù)退避策略,需設(shè)置參數(shù)sleeperinitialInterval、maxIntervalmultiplier,initialInterval指定初始休眠時(shí)間,默認(rèn)100毫秒,maxInterval指定最大休眠時(shí)間,默認(rèn)30秒,multiplier指定乘數(shù),即下一次休眠時(shí)間為當(dāng)前休眠時(shí)間*multiplier

          • ExponentialRandomBackOffPolicy: 隨機(jī)指數(shù)退避策略,引入隨機(jī)乘數(shù)可以實(shí)現(xiàn)隨機(jī)乘數(shù)回退

          我們可以根據(jù)自己的應(yīng)用場景和需求,使用不同的策略,不過一般使用默認(rèn)的就足夠了。

          上面的代碼的話,我簡單的設(shè)置了重試間隔為1秒,重試的異常是RemoteAccessException,下面就是測(cè)試代碼的情況: 重試第二次成功的情況:

          重試一次以后,遇到了沒有指出需要重試的異常,直接結(jié)束重試,調(diào)用retryContext

          重試了三次后,達(dá)到了最大重試次數(shù),調(diào)用retryContext

          2. Spring-Retry的注解使用方式

          既然是Spring家族的東西,那么自然就支持和Spring-Boot整合

          1.準(zhǔn)備工作

          依賴:

           <dependency>
              <groupId>org.springframework.retry</groupId>
              <artifactId>spring-retry</artifactId>
              <version>1.2.2.RELEASE</version>
           </dependency>

           <dependency>
              <groupId>org.aspectj</groupId>
              <artifactId>aspectjweaver</artifactId>
              <version>1.9.1</version>
           </dependency>
          2.代碼

          在application啟動(dòng)類上加上@EnableRetry的注解

          @EnableRetry
          public class Application {
           ...
          }

          為了方便測(cè)試,我這里寫了一個(gè)SpringBootTest的測(cè)試基類,需要使用SpringBootTest的只要繼承這個(gè)類就好了

          package com.zgd.demo.thread.test;

          /**
           * @Author: zgd
           * @Description:
           */


          import com.zgd.demo.thread.Application;
          import lombok.extern.slf4j.Slf4j;
          import org.junit.After;
          import org.junit.Before;
          import org.junit.runner.RunWith;
          import org.springframework.boot.test.context.SpringBootTest;
          import org.springframework.test.context.junit4.SpringRunner;

          /**
           * @Author: zgd
           * @Date: 18/09/29 20:33
           * @Description:
           */

          @RunWith(SpringRunner.class)
          @SpringBootTest(classes 
          = Application.class)
          @Slf4j
          public class MyBaseTest 
          {


            @Before
            public void init() {
              log.info("----------------測(cè)試開始---------------");
            }

            @After
            public void after() {
              log.info("----------------測(cè)試結(jié)束---------------");
            }

          }

          我們只要在需要重試的方法上加@Retryable,在重試失敗的回調(diào)方法上加@Recover,下面是這些注解的屬性

          建一個(gè)service類

          package com.zgd.demo.thread.retry.spring;

          import com.zgd.demo.thread.retry.RetryDemoTask;
          import com.zgd.demo.thread.test.MyBaseTest;
          import lombok.extern.slf4j.Slf4j;
          import org.springframework.remoting.RemoteAccessException;
          import org.springframework.retry.ExhaustedRetryException;
          import org.springframework.retry.annotation.Backoff;
          import org.springframework.retry.annotation.Recover;
          import org.springframework.retry.annotation.Retryable;
          import org.springframework.stereotype.Component;

          /**
           * @Author: zgd
           * @Description:
           */

          @Service
          @Slf4j
          public class SpringRetryDemo   {

           /**
             * 重試所調(diào)用方法
             * @param param
             * @return
             */

            @Retryable(value = {RemoteAccessException.class},maxAttempts 3,backoff = @Backoff(delay = 2000L,multiplier = 2))
            public boolean call(String param){
                return RetryDemoTask.retryTask(param);
            }

            /**
             * 達(dá)到最大重試次數(shù),或拋出了一個(gè)沒有指定進(jìn)行重試的異常
             * recover 機(jī)制
             * @param e 異常
             */

            @Recover
            public boolean recover(Exception e,String param) {
              log.error("達(dá)到最大重試次數(shù),或拋出了一個(gè)沒有指定進(jìn)行重試的異常:",e);
              return false;
            }

          }

          然后我們調(diào)用這個(gè)service里面的call方法

          package com.zgd.demo.thread.retry.spring;

          import com.zgd.demo.thread.test.MyBaseTest;
          import lombok.extern.slf4j.Slf4j;
          import org.junit.Test;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.stereotype.Component;

          /**
           * @Author: zgd
           * @Description:
           */

          @Component
          @Slf4j
          public class SpringRetryDemoTest extends MyBaseTest {

            @Autowired
            private SpringRetryDemo springRetryDemo;

            @Test
            public void retry(){
              boolean abc = springRetryDemo.call("abc");
              log.info("--結(jié)果是:{}--",abc);
            }

          }

          這里我依然是RemoteAccessException的異常才重試,@Backoff(delay = 2000L,multiplier = 2))表示第一次間隔2秒,以后都是次數(shù)的2倍,也就是第二次4秒,第三次6秒.

          來測(cè)試一下:

          遇到了沒有指定重試的異常,這里指定重試的異常是 @Retryable(value = {RemoteAccessException.class}...,所以拋出參數(shù)異常IllegalArgumentException的時(shí)候,直接回調(diào)@Recover的方法

          重試達(dá)到最大重試次數(shù)時(shí),調(diào)用@Recover的方法

          重試到最后一次沒有報(bào)錯(cuò),返回false

          二 重試框架之Guava-Retry

          Guava retryer工具與spring-retry類似,都是通過定義重試者角色來包裝正常邏輯重試,但是Guava retryer有更優(yōu)的策略定義,在支持重試次數(shù)和重試頻度控制基礎(chǔ)上,能夠兼容支持多個(gè)異?;蛘咦远x實(shí)體對(duì)象的重試源定義,讓重試功能有更多的靈活性。

          Guava Retryer也是線程安全的,入口調(diào)用邏輯采用的是Java.util.concurrent.Callable的call方法,示例代碼如下:

          pom.xml加入依賴

            <!-- 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>

          更改一下測(cè)試的任務(wù)方法

          package com.zgd.demo.thread.retry;

          import lombok.extern.slf4j.Slf4j;
          import org.apache.commons.lang3.RandomUtils;
          import org.springframework.remoting.RemoteAccessException;

          /**
           * @Author: zgd
           * @Description:
           */

          @Slf4j
          public class RetryDemoTask {


            /**
             * 重試方法
             * @return
             */

            public static boolean retryTask(String param)  {
              log.info("收到請(qǐng)求參數(shù):{}",param);

              int i = RandomUtils.nextInt(0,11);
              log.info("隨機(jī)生成的數(shù):{}",i);
              if (i < 2) {
                log.info("為0,拋出參數(shù)異常.");
                throw new IllegalArgumentException("參數(shù)異常");
              }else if (i  < 5){
                log.info("為1,返回true.");
                return true;
              }else if (i < 7){
                log.info("為2,返回false.");
                return false;
              }else{
                //為其他
                  log.info("大于2,拋出自定義異常.");
                  throw new RemoteAccessException("大于2,拋出自定義異常");
                }
              }

          }

          Guava

          這里設(shè)定跟Spring-Retry不一樣,我們可以根據(jù)返回的結(jié)果來判斷是否重試,比如返回false我們就重試

          package com.zgd.demo.thread.retry.guava;

          import com.github.rholder.retry.*;
          import com.zgd.demo.thread.retry.RetryDemoTask;
          import org.junit.Test;
          import org.springframework.remoting.RemoteAccessException;

          import java.util.concurrent.ExecutionException;
          import java.util.concurrent.TimeUnit;
          import java.util.function.Predicate;

          /**
           * @Author: zgd
           * @Description:
           */

          public class GuavaRetryTest {


            @Test
            public void fun01(){
              // RetryerBuilder 構(gòu)建重試實(shí)例 retryer,可以設(shè)置重試源且可以支持多個(gè)重試源,可以配置重試次數(shù)或重試超時(shí)時(shí)間,以及可以配置等待時(shí)間間隔
              Retryer<Boolean> retryer = RetryerBuilder.<Boolean> newBuilder()
                      .retryIfExceptionOfType(RemoteAccessException.class)//設(shè)置異常重試源
                      .retryIfResult(res-> res
          ==false)  //設(shè)置根據(jù)結(jié)果重試
                      .withWaitStrategy(WaitStrategies.fixedWait(3, TimeUnit.SECONDS)) //設(shè)置等待間隔時(shí)間
                      .withStopStrategy(StopStrategies.stopAfterAttempt(3)) //設(shè)置最大重試次數(shù)
                      .build();

              try {
                retryer.call(() -> RetryDemoTask.retryTask("abc"));
              } catch (Exception e) {
                e.printStackTrace();
              }
            }

          }

          運(yùn)行測(cè)試一下

          遇到了我們指定的需要重試的異常,進(jìn)行重試,間隔是3秒

          重試次數(shù)超過了最大重試次數(shù)

          返回為true,直接結(jié)束重試

          遇到了沒有指定重試的異常,結(jié)束重試

          返回false,重試

          我們可以更靈活的配置重試策略,比如:

          • retryIfException:retryIfException,拋出 runtime 異常、checked 異常時(shí)都會(huì)重試,但是拋出 error 不會(huì)重試。

          • retryIfRuntimeException:retryIfRuntimeException 只會(huì)在拋 runtime 異常的時(shí)候才重試,checked 異常和error 都不重試。

          • retryIfExceptionOfType:retryIfExceptionOfType 允許我們只在發(fā)生特定異常的時(shí)候才重試,比如NullPointerException 和 IllegalStateException 都屬于 runtime 異常,也包括自定義的error

          如:

          retryIfExceptionOfType(NullPointerException.class)// 只在拋出空指針異常重試
          • retryIfResult:retryIfResult 可以指定你的 Callable 方法在返回值的時(shí)候進(jìn)行重試,如
          // 返回false重試  
          .retryIfResult(Predicates.equalTo(false))   

          //以_error結(jié)尾才重試  
          .retryIfResult(Predicates.containsPattern("_error$"))

          //返回為空時(shí)重試
          .retryIfResult(res-> res==null)
          • RetryListener: 當(dāng)發(fā)生重試之后,假如我們需要做一些額外的處理動(dòng)作,比如log一下異常,那么可以使用RetryListener。每次重試之后,guava-retrying 會(huì)自動(dòng)回調(diào)我們注冊(cè)的監(jiān)聽??梢宰?cè)多個(gè)RetryListener,會(huì)按照注冊(cè)順序依次調(diào)用。
          .withRetryListener(new RetryListener {      
           @Override    
             public <T> void onRetry(Attempt<T> attempt) {  
                         logger.error("第【{}】次調(diào)用失敗" , attempt.getAttemptNumber());  
                    } 
           }

          總結(jié)

          spring-retry 和 guava-retry 工具都是線程安全的重試,能夠支持并發(fā)業(yè)務(wù)場景的重試邏輯正確性。兩者都很好的將正常方法和重試方法進(jìn)行了解耦,可以設(shè)置超時(shí)時(shí)間、重試次數(shù)、間隔時(shí)間、監(jiān)聽結(jié)果、都是不錯(cuò)的框架。

          但是明顯感覺得到,guava-retry在使用上更便捷,更靈活,能根據(jù)方法返回值來判斷是否重試,而Spring-retry只能根據(jù)拋出的異常來進(jìn)行重試。

          來源:blog.csdn.net/zzzgd_666/article/

          details/84377962

          干貨分享

          最近將個(gè)人學(xué)習(xí)筆記整理成冊(cè),使用PDF分享。關(guān)注我,回復(fù)如下代碼,即可獲得百度盤地址,無套路領(lǐng)?。?/p>

          ?001:《Java并發(fā)與高并發(fā)解決方案》學(xué)習(xí)筆記;?002:《深入JVM內(nèi)核——原理、診斷與優(yōu)化》學(xué)習(xí)筆記;?003:《Java面試寶典》?004:《Docker開源書》?005:《Kubernetes開源書》?006:《DDD速成(領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)速成)》?007:全部?008:加技術(shù)群討論

          加個(gè)關(guān)注不迷路

          喜歡就點(diǎn)個(gè)"在看"唄^_^

          瀏覽 81
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美性生交大片免费看A片免费 | 国产欧美黄色一级二级三级 | 欧美黄色电影一区二区在线播放 | 四虎AⅤ免费影院 | 国产操逼视频。 |