JavaのRetry再試行メカニズムの詳細

9682 ワード

アプリケーションでは、リモートストレージサービスにデータをアップロードし、処理に成功した場合に他の操作を行う機能が必要です.この機能は複雑ではなく、2つのステップに分けられる:第1ステップはリモートのRestサービスを呼び出してデータをアップロードした後、返された結果を処理する;第2のステップは、第1のステップの結果を取得するか、異常をキャプチャします.エラーまたは異常が発生した場合、アップロードロジックを再試行します.そうしないと、次の機能ビジネス操作を続行します.
一般的なソリューション
try-catch-redo単純再試行モード
パッケージの正常なアップロードロジックに基づいて、戻り結果またはリスニング異常を判断することによって再試行するかどうかを決定するとともに、直ちに再試行する無効な実行(異常が外部実行不安定によるネットワークジッタであると仮定)を解決するために、一定の遅延時間を休眠した後に機能ロジックを再実行する.
public void commonRetry(Map dataMap) throws InterruptedException { 
    Map paramMap = Maps.newHashMap(); 
    paramMap.put("tableName", "creativeTable"); 
    paramMap.put("ds", "20160220"); 
    paramMap.put("dataMap", dataMap); 
    boolean result = false; 
    try { 
      result = uploadToOdps(paramMap); 
      if (!result) { 
        Thread.sleep(1000); 
        uploadToOdps(paramMap); //     
      } 
    } catch (Exception e) { 
      Thread.sleep(1000); 
      uploadToOdps(paramMap);//     
    } 
  }

try-catch-redo-retry strategyポリシー再試行モード
上記のスキームは,再試行が無効である可能性もあり,この問題を解決して再試行回数retrycountおよび再試行間隔周期intervalを増加させ,再試行の有効性を増加させることを試みる.
public void commonRetry(Map dataMap) throws InterruptedException { 
    Map paramMap = Maps.newHashMap(); 
    paramMap.put("tableName", "creativeTable"); 
    paramMap.put("ds", "20160220"); 
    paramMap.put("dataMap", dataMap); 
    boolean result = false; 
    try { 
      result = uploadToOdps(paramMap); 
      if (!result) { 
        reuploadToOdps(paramMap,1000L,10);//       
      } 
    } catch (Exception e) { 
      reuploadToOdps(paramMap,1000L,10);//       
    } 
  }

シナリオ1とシナリオ2には1つの問題がある:正常論理と再試行論理が強く結合し、再試行論理は正常論理の実行結果に非常に依存し、正常論理の予想結果に対して受動的に再試行トリガし、再試行の根源は往々にして論理が複雑で水没し、後続の運行次元が再試行論理がどのような問題を解決するかについて不一致な理解を生む可能性がある.再試行の正確性は保証しにくく、メンテナンスに不利である.なぜなら、再試行設計が正常な論理異常または再試行の根源に依存する憶測のためである.
エレガントな再試行スキームの試み
コマンド設計モードを適用して通常と再試行ロジックをデカップリングする
コマンド設計モードの具体的な定義は説明を展開しないで、主にこの方案はコマンドモードが実行対象を通じてインタフェースの操作ロジックを完成することができることを気に入って、同時に内部パッケージ処理はロジックを再試行して、実現の細部を暴露しないで、呼び出し者にとって正常なロジックを実行して、デカップリングの目標を達成して、具体的に機能の実現を見ます.(クラス図構造)
Iretryはアップロードと再試行インタフェースを約束し、実際にはクラスOdpsRetryがODPSアップロードロジックをカプセル化し、同時に再試行メカニズムと再試行戦略をカプセル化する.同時にrecoverメソッドを使用してリカバリ操作を終了します.
我々の呼び出し者LogicClientは再試行に注目する必要はなく、再試行者Retryerによって所定のインタフェース機能を実現すると同時に、Retryerは再試行ロジックに応答して処理する必要があり、Retryerの具体的な再試行処理は真のIrtryインタフェースの実装クラスOdpsRetryに渡されて完了する.コマンドモードを採用することによって、正常論理と再試行論理の分離を優雅に実現し、同時に再試行者の役割を構築することによって、正常論理と再試行論理の分離を実現し、再試行により良い拡張性をもたらす.
Guava retryerの優雅なインタフェース再調整メカニズムを使用
Guava retryerツールはspring-retryと同様に、通常の論理再試行を再試行者ロールを定義することでパッケージ化されていますが、Guava retryerにはより優れたポリシー定義があり、再試行回数と再試行頻度制御をサポートした上で、複数の異常またはカスタムボディオブジェクトをサポートする再試行ソース定義と互換性があり、再試行機能の柔軟性を高めることができます.Guava Retryerもスレッドが安全で、エントリ呼び出しロジックはJavaを採用している.util.concurrent.Callableのcallメソッド.Guava retryerを使用するのは簡単です.次の手順に従います.
  • Maven POM導入
  • 2.0.0
    
          com.github.rholder
          guava-retrying
          ${guava-retry.version}
    
    
  • は、Guava retryerが
  • を呼び出すことができるように、Callableインタフェースを実装する方法を定義する.
    private static Callable updateReimAgentsCall = new Callable() {
       @Override
       public Boolean call() throws Exception {
           String url = ConfigureUtil.get(OaConstants.OA_REIM_AGENT);
           String result = HttpMethod.post(url, new ArrayList());
           if(StringUtils.isEmpty(result)){
              throw new RemoteException("  OA          ");
           }
           List oaReimAgents = JSON.parseArray(result, OAReimAgents.class);
           if(CollectionUtils.isNotEmpty(oaReimAgents)){
               CacheUtil.put(Constants.REIM_AGENT_KEY,oaReimAgents);
               return true;
           }
           return false;
       }
    };
    
  • Retryオブジェクトを定義し、関連ポリシー
  • を設定する.
    Retryer retryer = RetryerBuilder.newBuilder()
                    //  runtime  、checked       ,    error    。
                    .retryIfException()
                    //  false     
                    .retryIfResult(Predicates.equalTo(false))
                    //    
                    .withWaitStrategy(WaitStrategies.fixedWait(10, TimeUnit.SECONDS))
                    //    
                    .withStopStrategy(StopStrategies.stopAfterAttempt(3))
                    .build();
     
    try {
        retryer.call(updateReimAgentsCall());
        #                    Callable      
        //retry.call(() -> { FileUtils.downloadAttachment(projectNo, url, saveDir, fileName);  return true; });
    } catch (ExecutionException e) {
        e.printStackTrace();
    } catch (RetryException e) {
        logger.error("xxx");
    }
    

    簡単な3ステップでGuava Retryerの優雅な再調整方法を実現できます.
    その他の機能
    RetryerBuilderはFactoryの作成者で、再試行ソースの設定をカスタマイズし、複数の再試行ソースをサポートします.再試行回数または再試行タイムアウト時間を構成したり、待機時間間隔を構成したり、再試行者Retryerインスタンスを作成したりできます.RetryerBuilderの再試行ソースは、Exception例外オブジェクトとカスタムブレークスルーオブジェクトをサポートし、retryIfException retryIfResultで設定し、複数の互換性を同時にサポートします.
  • retryIfException:runtime異常、checked異常を投げ出すと再試行されますが、errorを投げ出すと再試行されません.
  • retryIfRuntimeException:runtime異常を投げたときにのみ再試行され、checked異常もerrorも再試行されません.
  • retryIfExceptionOfType:Null PointerExceptionやIllegalStateExceptionなど、特定の例外が発生した場合にのみ再試行できます.
    #     error  
    retryIfExceptionOfType(Error.class)     
    #                , :  
    retryIfExceptionOfType(IllegalStateException.class)  
    retryIfExceptionOfType(NullPointerException.class)  
    #     Predicate  
    retryIfException(Predicates.or(Predicates.instanceOf(NullPointerException.class),  
                    Predicates.instanceOf(IllegalStateException.class))) 
    

    retryIfResultは、値を返すときにCallableメソッドを再試行することを指定します.たとえば、
    //   false   
    retryIfResult(Predicates.equalTo(false))  
    // _error      
    retryIfResult(Predicates.containsPattern("_error$"))  
    

    再試行が発生した後、警告メールなどの追加の処理を行う必要がある場合は、RetryListenerを使用します.再試行するたびにguava-retryingは登録したリスニングを自動的にコールバックします.複数のRetryListenerを登録して、登録順に順番に呼び出すこともできます.
    import com.github.rholder.retry.Attempt;  
    import com.github.rholder.retry.RetryListener;  
    import java.util.concurrent.ExecutionException;  
      
    public class MyRetryListener implements RetryListener {  
        @Override  
        public  void onRetry(Attempt attempt) {  
            //      ,(  :             )  
            System.out.print("[retry]time=" + attempt.getAttemptNumber());  
            //             
            System.out.print(",delay=" + attempt.getDelaySinceFirstAttempt());  
            //     :      ,         
            System.out.print(",hasException=" + attempt.hasException());  
            System.out.print(",hasResult=" + attempt.hasResult());  
            //            
            if (attempt.hasException()) {  
                System.out.print(",causeBy=" + attempt.getExceptionCause().toString());  
            } else {  
                //           
                System.out.print(",result=" + attempt.getResult());  
            }  
      
            // bad practice:               
            try {  
                Boolean result = attempt.get();  
                System.out.print(",rude get=" + result);  
            } catch (ExecutionException e) {  
                System.err.println("this attempt produce exception." + e.getCause().toString());  
            }  
            System.out.println();  
        }  
    } 
    

    次に、Retryオブジェクトでリスニングを指定します:withRetryListener(new MyRetryListener<>())転載先:https://juejin.im/post/5cdb81156fb9a03202223d15