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

          Java開發(fā)利器之重試器

          共 4185字,需瀏覽 9分鐘

           ·

          2021-02-06 12:12




          業(yè)務(wù)場景:
          1. 代碼中存在依賴不穩(wěn)定的場景,需要使用重試獲取預(yù)期結(jié)果或者嘗試重新執(zhí)行邏輯不立即結(jié)束,比如遠(yuǎn)程接口訪問,數(shù)據(jù)加載訪問,數(shù)據(jù)上傳校驗(yàn)等
          2. 對于異常需要重試的場景,同時(shí)希望把正常邏輯和重試邏輯解耦
          3. 對方接口不支持異步回調(diào)

          在平時(shí)開發(fā)中經(jīng)常會遇到需要調(diào)用接口和外部服務(wù)的場景,但是有些接口服務(wù)方不能立即返回?cái)?shù)據(jù),而是需要處理一段時(shí)間才能返回真實(shí)的業(yè)務(wù)數(shù)據(jù),如果沒有處理完則直接返回一個(gè)中間狀態(tài)的結(jié)果。

          類似于{"errorCode":"-1", "errorMsg":"處理中", "result":""}這樣的結(jié)果,然后調(diào)用方需要過段時(shí)間(一般間隔幾秒種后,需要根據(jù)具體的業(yè)務(wù)確認(rèn))再次調(diào)用才能獲取真實(shí)的結(jié)果。

          傳統(tǒng)的寫法如下(偽代碼):
          int time = 0;do {????time++;    result = testRedo(); // 調(diào)用接口} while (null == result && time < 5);

          對于這種場景大家或多或少都遇到過,但上面這樣的寫法有幾個(gè)很明顯的弊端:
          1. ?調(diào)用方需要不僅需要考慮多次調(diào)用的次數(shù),還要考慮每次調(diào)用的間隔時(shí)間,盡量在最少調(diào)用情況下獲取到最終結(jié)果(多一次請求意味著多一次網(wǎng)絡(luò)開銷,不方便實(shí)時(shí)調(diào)整)
          2. 多次調(diào)用過程中偶爾有一次調(diào)用出現(xiàn)異常(接口報(bào)錯,網(wǎng)絡(luò)異常),如果沒有異常處理就會影響剩下次數(shù)的調(diào)用,無法保證高可用
          3. 多線程情況下上面的代碼會出現(xiàn)并發(fā)問題,因?yàn)榈谝淮握{(diào)用的結(jié)果不一定是最早返回的,有可能后面調(diào)用的先返回,導(dǎo)致結(jié)果不是預(yù)期的
          4. 性能問題,如果使用多線程要考慮線程創(chuàng)建銷毀和切換問題

          當(dāng)然這些問題自己實(shí)現(xiàn)完全可以解決,但已經(jīng)有現(xiàn)成的輪子我們可以直接拿來用

          上述場景可以考慮使用google的guava-retry工具,guava-retryer的特點(diǎn)如下:
          1. 支持設(shè)置重試次數(shù)和間隔時(shí)間,支持多種復(fù)雜場景的重試策略,延遲策略
          2. 而且支持多個(gè)異常或者自定義實(shí)體對象的重試源,讓重試功能有更多的靈活性
          3. 線程安全,我們只需要關(guān)注我們的業(yè)務(wù)邏輯實(shí)現(xiàn)即可
          4. 內(nèi)部使用線程池管理線程
          5. 基于命令模式使用鏈?zhǔn)秸{(diào)用,使用方便


          pom依賴:

          <dependency>????<groupId>com.github.rholdergroupId>????<artifactId>guava-retryingartifactId>    <version>2.0.0version>dependency>
          git?地址:https://github.com/rholder/guava-retrying

          下面的代碼模擬接口調(diào)用,設(shè)置重試次數(shù)為5次,每次調(diào)用間隔為2秒,如果調(diào)用過程中出現(xiàn)異常或結(jié)果滿足重試條件的則再次調(diào)用直到最大次數(shù)(拋出異 常):
          // 重試條件Predicate<String> condition = response -> Objects.nonNull(response)     && "處理中".equals(response.getReturnCode());
          Optional<String> response = RetryUtil.retry(????????????????condition,?//?重試條件 () -> invoke(request), // 重試任務(wù)(比如調(diào)用接口)????????????????500,?//?500ms重試一次, 可以做成配置化 3); // 一共重試3次, 可以做成配置化
          return response.orElse(null);
          RetryUtil是我對guava-retrying的封裝實(shí)現(xiàn),下面的代碼大家可以直接拿去使用,只需要按照業(yè)務(wù)改下重試條件和重試任務(wù)以及重試間隔和次數(shù)即可:
          /** * 根據(jù)輸入的condition重復(fù)做task,在規(guī)定的次數(shù)內(nèi)達(dá)到condition則返回, * 如果超過retryTimes則返回null, 重試次數(shù),整個(gè)重試時(shí)間以及retry exception都會記錄log * * @param condition  重試條件,比如接口返回errorCode為處理中,或不是最終需要的結(jié)果 * @param task       重試做的任務(wù) * @param sleepTime  重試間隔時(shí)間,單位毫秒 * @param retryTimes 重試次數(shù) * @return targetBean */public static  Optional retry(Predicate condition, Callable task, int sleepTime, int retryTimes) {    Optional result = Optional.empty();    StopWatch stopWatch = new StopWatch();????try?{        stopWatch.start();        Retryer retry = RetryerBuilder.newBuilder()                // 默認(rèn)任務(wù)執(zhí)行過程中發(fā)生異常自動重試                .retryIfException()                // 重試條件(按照業(yè)務(wù)場景)                .retryIfResult(condition)                // 等待策略                .withWaitStrategy(WaitStrategies.fixedWait(sleepTime, TimeUnit.MILLISECONDS))                // 重試策略                .withStopStrategy(StopStrategies.stopAfterAttempt(retryTimes))                // 重試監(jiān)聽器                .withRetryListener(new RetryListener() {                    @Override                    public  void onRetry(Attempt attempt) {                        // 記錄重試次數(shù)和異常信息                                                log.info(MessageFormat.format("{0}th retry", attempt.getAttemptNumber()));                        if (attempt.hasException()) {                            log.error(MessageFormat.format("retry exception:{0}", attempt.getExceptionCause()));                        }                    }                }).build();
          // 開始執(zhí)行重試任務(wù) result = Optional.ofNullable(retry.call(task)); } catch (Exception e) { log.error("retry fail:", e.getMessage()); } finally { stopWatch.stop(); log.info("retry execute time", stopWatch.getTime()); } return result;}
          重試間隔時(shí)間和重試次數(shù)可以做成可配置的,方便后續(xù)根據(jù)日志記錄觀察調(diào)整

          相關(guān)重試策略和api介紹:
          • AttemptTimeLimiter:單次任務(wù)執(zhí)行時(shí)間限制(如果單次任務(wù)執(zhí)行超時(shí),則終止執(zhí)行當(dāng)前任務(wù))

          • BlockStrategies:任務(wù)阻塞策略,默認(rèn)策略為:BlockStrategies.THREAD_SLEEP_STRATEGY,也就是調(diào)用Thread.sleep ()

          • StopStrategy:停止重試策略,提供三種:
            StopAfterDelayStrategy:設(shè)定一個(gè)最長允許的執(zhí)行時(shí)間,比如設(shè)定最長執(zhí)行10s,無論任務(wù)執(zhí)行次數(shù),只要重試的時(shí)候超出了最長時(shí)間,則任務(wù)終止,并返回重試異常RetryException
            NeverStopStrategy:不停止,用于需要一直輪詢直到返回期望結(jié)果的情況
          ??????? StopAfterAttemptStrategy:設(shè)定最大重試次數(shù),如果超出最大重試次數(shù)則停止重試,并返回重試異常

          • WaitStrategy:等待時(shí)長策略(控制時(shí)間間隔),返回結(jié)果為下次執(zhí)行時(shí)長:
          ??? ??? FixedWaitStrategy:固定等待時(shí)長策略
          ??? ??? RandomWaitStrategy:隨機(jī)等待時(shí)長策略
          ??? ??? IncrementingWaitStrategy:遞增等待時(shí)長策略
          ??? ??? ExponentialWaitStrategy:指數(shù)等待時(shí)長策略
          ??? ??? FibonacciWaitStrategy:斐波那契等待時(shí)長策略
          ??? ??? ExceptionWaitStrategy:異常時(shí)長等待策略
          ??? ??? CompositeWaitStrategy:復(fù)合時(shí)長等待策略

          除了google的retry外,spring框架也提供了一個(gè)重試工具:spring-retry,該工具把重試操作模板定制化。還有RxJava里有個(gè)retry的api也能實(shí)現(xiàn)類似的用法,感興趣的同學(xué)可以研究下。


          點(diǎn)個(gè)在看支持我吧,轉(zhuǎn)發(fā)就更好了
          瀏覽 46
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  少妇拍拍| 人人婷人人操 | 操操操视频 | 男操女逼射逼心国产中字传媒视频 | 在线视频1区 |