

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 重試失敗的活動
<a name="features-retry"></a>

活動有時會因暫時性原因失敗，例如暫時失去連線。有時活動可能成功，所以處理活動錯誤的適當方法，通常是重試活動，或許要多試幾次。

重試這些活動有各種策略，最好的策略是根據您的工作流程詳細資訊。這些策略分為三大基本分類：
+ 重試到成功為止策略只會一直重試活動直到完成。
+ 指數重試策略會以指數方式增加重試嘗試之間的時間間隔，直到活動完成或程序達到指定的停止點，例如嘗試次數的上限。
+ 自訂重試策略決定是否以及如何在每次嘗試失敗後重試活動。

以下各節會說明如何實作這些策略。範例工作流程工作者全都使用單一活動 `unreliableActivity`，隨機執行下列作業之一：
+ 立即完成
+ 超過逾時值故意失敗
+ 拋出 `IllegalStateException` 故意失敗 

## 重試到成功為止策略
<a name="features-retry-success"></a>

最簡單的重試策略是每次活動失敗就一直重試，直到最後成功。基本模式是：

1. 實作您工作流程進入點方法的 `TryCatch` 或 `TryCatchFinally` 類別。

1. 在 `doTry` 中執行活動

1. 如果活動失敗，框架會呼叫 `doCatch`，再次執行進入點方法。

1. 重複步驟 2 - 3 直到順利完成活動。

以下工作流程會實作重試到成功為止策略。工作流程界面在 `RetryActivityRecipeWorkflow` 中實作，且有一個方法 `runUnreliableActivityTillSuccess`，這是工作流程的進入點。工作流程工作者在 `RetryActivityRecipeWorkflowImpl` 中實作，如下所示：

```
public class RetryActivityRecipeWorkflowImpl
    implements RetryActivityRecipeWorkflow {

    @Override
    public void runUnreliableActivityTillSuccess() {
        final Settable<Boolean> retryActivity = new Settable<Boolean>();

        new TryCatch() {
            @Override
            protected void doTry() throws Throwable {
                Promise<Void> activityRanSuccessfully
                    = client.unreliableActivity();
                setRetryActivityToFalse(activityRanSuccessfully, retryActivity);
            }

            @Override
            protected void doCatch(Throwable e) throws Throwable {
                retryActivity.set(true);
            }
        };
        restartRunUnreliableActivityTillSuccess(retryActivity);
    }

    @Asynchronous
    private void setRetryActivityToFalse(
            Promise<Void> activityRanSuccessfully,
            @NoWait Settable<Boolean> retryActivity) {
        retryActivity.set(false);
    }

    @Asynchronous
    private void restartRunUnreliableActivityTillSuccess(
            Settable<Boolean> retryActivity) {
        if (retryActivity.get()) {
            runUnreliableActivityTillSuccess();
        }
    }
}
```

工作流程運作方式如下：

1. `runUnreliableActivityTillSuccess` 會建立 `Settable<Boolean>` 物件，名稱為 `retryActivity`，其用於指出活動是否失敗，以及是否應該重試。`Settable<T>` 是衍生自 `Promise<T>` 並且運作方式相同，但是您手動設定 `Settable<T>` 物件的值。

1. `runUnreliableActivityTillSuccess` 實作匿名的巢狀 `TryCatch` 類別，以處理 `unreliableActivity` 活動拋出的任何例外狀況。如需深入討論如何處理匿名程式碼拋出的例外狀況，請參閱「[錯誤處理](errorhandling.md)」。

1. `doTry` 執行 `unreliableActivity` 活動，這樣會傳回 `Promise<Void>` 物件，名為 `activityRanSuccessfully`。

1. `doTry` 呼叫非同步的 `setRetryActivityToFalse` 方法，它有兩個參數：
   + `activityRanSuccessfully` 會採用 `unreliableActivity` 活動傳回的 `Promise<Void>` 物件。
   + `retryActivity` 採用 `retryActivity` 物件。

   當 `unreliableActivity` 完成後，`activityRanSuccessfully` 就會就緒，且 `setRetryActivityToFalse` 會將 `retryActivity` 設為 false。否則，`activityRanSuccessfully` 絕不會就緒，而 `setRetryActivityToFalse` 不執行。

1. 如果 `unreliableActivity` 擲出例外狀況，框架就會呼叫 `doCatch` 並將例外狀況物件傳遞給它。`doCatch` 將 `retryActivity` 設定為 true。

1. `runUnreliableActivityTillSuccess` 呼叫非同步的 `restartRunUnreliableActivityTillSuccess` 方法，並將 `retryActivity` 物件傳遞給它。因為 `retryActivity` 是 `Promise<T>` 類型，所以 `restartRunUnreliableActivityTillSuccess` 延遲執行直到 `retryActivity` 就緒為止，這會在 `TryCatch` 完成後發生。

1. 當 `retryActivity` 就緒時，`restartRunUnreliableActivityTillSuccess` 會擷取值。
   + 如果該值為 `false`，表示重試成功。`restartRunUnreliableActivityTillSuccess` 不執行任何動作，且重試序列會終止。
   + 如果值為 true，表示重試失敗。`restartRunUnreliableActivityTillSuccess` 會呼叫 `runUnreliableActivityTillSuccess` 以再次執行活動。

1. 重複步驟 1 - 7 直到 `unreliableActivity` 完成。

**注意**  
`doCatch` 不處理例外狀況，只將 `retryActivity` 物件設成 true，指出活動失敗。重試是由非同步的 `restartRunUnreliableActivityTillSuccess` 方法處理，這會延遲例外狀況直到 `TryCatch` 完成。此方法的原因是，如果您以 `doCatch` 重試活動，您就無法取消它。以 `restartRunUnreliableActivityTillSuccess` 重試活動可讓您執行可取消的活動。

## 指數重試策略
<a name="features-retry-exponential"></a>

使用指數重試策略，框架會在指定期間後 (N 秒) 再次執行失敗的活動。如果該嘗試失敗，框架就會在 2N 秒後、4N 秒後、以此類推，再次執行活動。因為等待時間會變得相當長，您一般會在某個時間點停止重試嘗試，而不是無止境地繼續下去。

框架提供三種方式實作指數重試策略：
+ `@ExponentialRetry` 註釋是最簡單的方法，但您必須在編譯階段設定重試組態選項。
+ `RetryDecorator` 類別可讓您在執行時間設定重試組態，並視需要予以變更。
+ `AsyncRetryingExecutor` 類別可讓您在執行時間設定重試組態，並視需要予以變更。此外，框架會呼叫使用者實作的 `AsyncRunnable.run` 方法，執行每次的重試嘗試。

所有方法都支援下列組態選項，它們的時間值都是以秒計：
+ 初始重試等待時間。
+ 用來計算重試間隔的退避係數，如下所示：

  ```
  retryInterval = initialRetryIntervalSeconds * Math.pow(backoffCoefficient, numberOfTries - 2)
  ```

  預設值為 2.0。
+ 重試嘗試次數的上限。預設值無限制。
+ 重試間隔上限。預設值無限制。
+ 過期時間。當程序期間總計超過此值時就會停止重試嘗試。預設值無限制。
+ 會觸發重試程序的例外狀況。根據預設，每種例外狀況都會觸發重試程序。
+ 不會觸發重試嘗試的例外狀況。根據預設，不排除任何例外狀況。

以下各節說明您可實作指數重試策略的各種方式。

### 使用 @ExponentialRetry 的指數重試
<a name="features-retry-exponential-annotation"></a>

為活動實作指數重試策略最簡單的方式，是在界面定義中將 `@ExponentialRetry ` 註釋套用到活動。如果活動失敗，框架會根據指定的選項值，自動處理重試程序。基本模式是：

1. 將 `@ExponentialRetry` 套用到合適的活動並指定重試組態。

1. 如果註釋的活動失敗，框架會根據註釋引數指定的組態，自動重試活動。

`ExponentialRetryAnnotationWorkflow` 工作流程工作者使用 `@ExponentialRetry` 註釋實作指數重試策略。它使用 `unreliableActivity` 活動，它的界面定義是以 `ExponentialRetryAnnotationActivities` 實作，如下所示：

```
@Activities(version = "1.0")
@ActivityRegistrationOptions(
    defaultTaskScheduleToStartTimeoutSeconds = 30,
    defaultTaskStartToCloseTimeoutSeconds = 30)
public interface ExponentialRetryAnnotationActivities {
    @ExponentialRetry(
        initialRetryIntervalSeconds = 5,
        maximumAttempts = 5,
        exceptionsToRetry = IllegalStateException.class)
    public void unreliableActivity();
}
```

`@ExponentialRetry` 選項指定以下策略：
+ 只有當活動拋出 `IllegalStateException` 時才重試。
+ 使用 5 秒的初始等待時間。
+ 不超過 5 次重試嘗試。

工作流程界面在 `RetryWorkflow` 中實作，且有一個方法 `process`，這是工作流程的進入點。工作流程工作者在 `ExponentialRetryAnnotationWorkflowImpl` 中實作，如下所示：

```
public class ExponentialRetryAnnotationWorkflowImpl implements RetryWorkflow {
    public void process() {
        handleUnreliableActivity();
    }

    public void handleUnreliableActivity() {
        client.unreliableActivity();
    }
}
```

工作流程運作方式如下：

1. `process` 執行同步的 `handleUnreliableActivity` 方法。

1. `handleUnreliableActivity` 執行 `unreliableActivity` 活動。

如果活動因拋出 `IllegalStateException` 而失敗，框架會自動執行 `ExponentialRetryAnnotationActivities` 指定的重試策略。

### 使用 RetryDecorator 類別的指數重試
<a name="features-retry-exponential-decorator"></a>

`@ExponentialRetry` 簡單好用。不過，組態是靜態的且於編譯階段設定，所以每次活動失敗，框架都會使用相同的重試策略。您可以使用 `RetryDecorator` 類別，實作更有彈性的指數重試策略，這可讓您在執行時間指定組態，並視需要予以變更。基本模式是：

1. 建立並設定指定重試組態的 `ExponentialRetryPolicy` 物件。

1. 建立 `RetryDecorator` 物件，並將步驟 1 中的 `ExponentialRetryPolicy` 物件傳遞到建構函數。

1. 將活動用戶端的類別名稱傳遞到 `RetryDecorator` 物件的裝飾方法，將裝飾項目物件套用到活動。

1. 執行活動。

如果活動失敗，框架會根據 `ExponentialRetryPolicy` 物件的組態，重試活動。您可以修改此物件，視需要變更重試組態。

**注意**  
`@ExponentialRetry` 註釋和 `RetryDecorator` 類別互斥。您不能使用 `RetryDecorator` 動態覆寫 `@ExponentialRetry` 註釋指定的重試政策。

以下工作流程實作示範如何使用 `RetryDecorator` 類別來實作指數重試策略。它使用沒有 `@ExponentialRetry` 註釋的 `unreliableActivity` 活動。工作流程界面在 `RetryWorkflow` 中實作，且有一個方法 `process`，這是工作流程的進入點。工作流程工作者在 `DecoratorRetryWorkflowImpl` 中實作，如下所示：

```
public class DecoratorRetryWorkflowImpl implements RetryWorkflow {
   ...
  public void process() {
      long initialRetryIntervalSeconds = 5;
      int maximumAttempts = 5;
      ExponentialRetryPolicy retryPolicy = new ExponentialRetryPolicy(
              initialRetryIntervalSeconds).withMaximumAttempts(maximumAttempts);

      Decorator retryDecorator = new RetryDecorator(retryPolicy);
      client = retryDecorator.decorate(RetryActivitiesClient.class, client);
      handleUnreliableActivity();
  }

  public void handleUnreliableActivity() {
      client.unreliableActivity();
  }
}
```

工作流程運作方式如下：

1. `process` 建立並設定 `ExponentialRetryPolicy` 物件的方法：
   + 將初始重試間隔傳遞到建構函數。
   + 呼叫物件的 `withMaximumAttempts` 方法來設定嘗試次數上限為 5。`ExponentialRetryPolicy` 會公開您可以用來指定其他組態選項的其他 `with` 物件。

1. `process` 建立名為 `retryDecorator` 的 `RetryDecorator` 物件，並將步驟 1 中的 `ExponentialRetryPolicy` 物件傳遞到建構函數。

1. `process` 透過呼叫 `retryDecorator.decorate` 方法並將活動用戶端的類別名稱傳遞給它，將裝飾項目套用到活動。

1. `handleUnreliableActivity` 執行活動。

如果活動失敗，框架會根據步驟 1 指定的組態，重試活動。

**注意**  
`ExponentialRetryPolicy` 類別的數個 `with` 方法有對應的 `set` 方法，您可隨時呼叫以修改對應的組態選項：`setBackoffCoefficient`、`setMaximumAttempts`、`setMaximumRetryIntervalSeconds` 和 `setMaximumRetryExpirationIntervalSeconds`。

### 使用 AsyncRetryingExecutor 類別的指數重試
<a name="features-retry-exponential-async"></a>

`RetryDecorator` 類別設定重試程序比 `@ExponentialRetry` 更有彈性，但是框架仍會根據 `ExponentialRetryPolicy` 物件目前的組態自動執行重試嘗試。更彈性的方法是使用 `AsyncRetryingExecutor` 類別。除在執行時間讓您設定重試程序之外，框架還會呼叫使用者實作的 `AsyncRunnable.run` 方法來執行每次的重試嘗試，不只是執行活動。

基本模式是：

1. 建立並設定 `ExponentialRetryPolicy` 物件以指定重試組態。

1. 建立 `AsyncRetryingExecutor` 物件，並將 `ExponentialRetryPolicy` 物件和工作流程時鐘執行個體傳遞給它。

1.  實作匿名的巢狀 `TryCatch` 或 `TryCatchFinally` 類別。

1. 實作匿名的 `AsyncRunnable` 類別並覆寫 `run` 方法，實作自訂的程式碼來執行活動。

1.  覆寫 `doTry` 來呼叫 `AsyncRetryingExecutor` 物件的 `execute` 方法，並將步驟 4 的 `AsyncRunnable` 類別傳遞給它。`AsyncRetryingExecutor` 物件呼叫 `AsyncRunnable.run` 執行活動。

1. 如果活動失敗，`AsyncRetryingExecutor` 物件會根據步驟 1 指定的重試政策，再次呼叫 `AsyncRunnable.run` 方法。

以下工作流程示範如何使用 `AsyncRetryingExecutor` 類別來實作指數重試策略。它會和前文討論的 `DecoratorRetryWorkflow` 工作流程使用相同的 `unreliableActivity` 活動。工作流程界面在 `RetryWorkflow` 中實作，且有一個方法 `process`，這是工作流程的進入點。工作流程工作者在 `AsyncExecutorRetryWorkflowImpl` 中實作，如下所示：

```
public class AsyncExecutorRetryWorkflowImpl implements RetryWorkflow {
  private final RetryActivitiesClient client = new RetryActivitiesClientImpl();
  private final DecisionContextProvider contextProvider = new DecisionContextProviderImpl();
  private final WorkflowClock clock = contextProvider.getDecisionContext().getWorkflowClock();

  public void process() {
      long initialRetryIntervalSeconds = 5;
      int maximumAttempts = 5;
      handleUnreliableActivity(initialRetryIntervalSeconds, maximumAttempts);
  }
  public void handleUnreliableActivity(long initialRetryIntervalSeconds, int maximumAttempts) {

      ExponentialRetryPolicy retryPolicy = new ExponentialRetryPolicy(initialRetryIntervalSeconds).withMaximumAttempts(maximumAttempts);
      final AsyncExecutor executor = new AsyncRetryingExecutor(retryPolicy, clock);

      new TryCatch() {
          @Override
          protected void doTry() throws Throwable {
              executor.execute(new AsyncRunnable() {
                  @Override
                  public void run() throws Throwable {
                      client.unreliableActivity();
                  }
              });
          }
          @Override
          protected void doCatch(Throwable e) throws Throwable {
          }
      };
  }
}
```

工作流程運作方式如下：

1. `process` 呼叫 `handleUnreliableActivity` 方法，並將組態設定傳遞給它。

1. `handleUnreliableActivity` 使用步驟 1 的組態設定建立 `ExponentialRetryPolicy` 物件 `retryPolicy`。

1. `handleUnreliableActivity` 建立 `AsyncRetryExecutor` 物件 `executor`，並將步驟 2 的 `ExponentialRetryPolicy` 物件和工作流程時鐘執行個體傳遞到建構函數。

1.  `handleUnreliableActivity` 實作匿名的巢狀 `TryCatch` 類別，並覆寫 `doTry` 和 `doCatch` 方法來執行重試嘗試及處理任何例外狀況。

1. `doTry` 建立匿名的 `AsyncRunnable` 類別並覆寫 `run` 方法，實作自訂的程式碼來執行 `unreliableActivity`。為簡化起見，`run` 只執行活動，但您可視情況實作更成熟的方法。

1. `doTry` 呼叫 `executor.execute` 並將 `AsyncRunnable` 物件傳遞給它。`execute` 呼叫 `AsyncRunnable` 物件的 `run` 方法來執行活動。

1. 如果活動失敗，執行器會根據 `retryPolicy` 物件組態再次呼叫 `run`。

如需深入討論如何使用 `TryCatch` 類別處理錯誤，請參閱「[AWS Flow Framework 適用於 Java 的例外狀況](errorhandling.exceptions.md)」。

## 自訂重試策略
<a name="custom-retry-strategy"></a>

重試失敗活動最有彈性的方法是自訂策略，遞迴呼叫執行重試嘗試的非同步方法，非常類似重試到成功為止策略。但您不僅僅可以再次執行活動，還可實作自訂邏輯來決定是否及如何執行每次接續的重試嘗試。基本模式是：

1. 建立 `Settable<T>` 狀態物件，用以指示活動是否失敗。

1. 實作巢狀的 `TryCatch` 或 `TryCatchFinally` 類別。

1. `doTry` 執行活動。

1. 如果活動失敗，`doCatch` 會設定狀態物件指出活動失敗。

1. 呼叫非同步的錯誤處理方法，並將狀態物件傳遞給它。此方法會延遲例外狀況直到 `TryCatch` 或 `TryCatchFinally` 完成。

1. 錯誤處理方法決定是否重試活動，如果重試，何時重試。

以下工作流程示範如何實作自訂的重試策略。它會和 `DecoratorRetryWorkflow` 與 `AsyncExecutorRetryWorkflow` 工作流程使用相同的 `unreliableActivity` 活動。工作流程界面在 `RetryWorkflow` 中實作，且有一個方法 `process`，這是工作流程的進入點。工作流程工作者在 `CustomLogicRetryWorkflowImpl` 中實作，如下所示：

```
public class CustomLogicRetryWorkflowImpl implements RetryWorkflow {
  ...
  public void process() {
      callActivityWithRetry();
  }
  @Asynchronous
  public void callActivityWithRetry() {
      final Settable<Throwable> failure = new Settable<Throwable>();
      new TryCatchFinally() {
          protected void doTry() throws Throwable {
              client.unreliableActivity();
          }
          protected void doCatch(Throwable e) {
              failure.set(e);
          }
          protected void doFinally() throws Throwable {
              if (!failure.isReady()) {
                  failure.set(null);
              }
          }
      };
      retryOnFailure(failure);
  }
  @Asynchronous
  private void retryOnFailure(Promise<Throwable> failureP) {
      Throwable failure = failureP.get();
      if (failure != null && shouldRetry(failure)) {
          callActivityWithRetry();
      }
  }
  protected Boolean shouldRetry(Throwable e) {
      //custom logic to decide to retry the activity or not
      return true;
  }
}
```

工作流程運作方式如下：

1. `process` 呼叫非同步的 `callActivityWithRetry` 方法。

1. `callActivityWithRetry` 會建立 `Settable<Throwable>` 物件，名稱為 failure，其用於指出活動是否已失敗。`Settable<T>` 是衍生自 `Promise<T>` 並且運作方式相同，但是您手動設定 `Settable<T>` 物件的值。

1. `callActivityWithRetry` 實作匿名的巢狀 `TryCatchFinally` 類別，以處理 `unreliableActivity` 拋出的任何例外狀況。如需深入討論如何處理匿名程式碼拋出的例外狀況，請參閱「[AWS Flow Framework 適用於 Java 的例外狀況](errorhandling.exceptions.md)」。

1. `doTry` 執行 `unreliableActivity`。

1. 如果 `unreliableActivity` 擲出例外狀況，框架會呼叫 `doCatch` 並傳遞例外狀況給它。`doCatch` 會將 `failure` 設定為例外狀況物件，指出活動失敗並將物件放在就緒狀態。

1. `doFinally` 檢查 `failure` 是否就緒，只有當 `failure` 是由 `doCatch` 所設定時才為 true。
   + 如果 `failure` 準備就緒， 不會`doFinally`執行任何動作。
   + 如果 `failure` 尚未就緒，活動完成且 `doFinally` 將錯誤設為 `null`。

1. `callActivityWithRetry` 呼叫非同步的 `retryOnFailure` 方法，並將錯誤傳遞給它。因為錯誤是 `Settable<T>` 類型，所以 `callActivityWithRetry` 會延遲執行直到錯誤就緒為止，這會在 `TryCatchFinally` 完成後發生。

1. `retryOnFailure` 從錯誤取得值。
   + 如果錯誤設成 null，重試嘗試就會成功。`retryOnFailure` 不執行任何動作，這會終止重試程序。
   + 如果錯誤設成例外狀況物件且 `shouldRetry` 傳回 true，`retryOnFailure` 會呼叫 `callActivityWithRetry` 重試活動。

     `shouldRetry` 實作自訂邏輯以決定是否重試失敗的活動。為簡化起見，`shouldRetry` 一律傳回 `true`，而 `retryOnFailure` 會立即執行活動，但您可視需要實作更成熟的邏輯。

1. 步驟 2-8 會重複，直到 `unreliableActivity` 完成或`shouldRetry`決定停止程序為止。

**注意**  
`doCatch` 不處理重試程序，只會設定錯誤指出活動失敗。重試程序是由非同步的 `retryOnFailure` 方法處理，這會延遲例外狀況直到 `TryCatch` 完成。此方法的原因是，如果您以 `doCatch` 重試活動，您就無法取消它。以 `retryOnFailure` 重試活動可讓您執行可取消的活動。