重試失敗的活動 - AWS Flow Framework 對於爪哇

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

重試失敗的活動

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

重試這些活動有各種策略,最好的策略是根據您的工作流程詳細資訊。這些策略分為三大基本分類:

  • 重試到成功為止策略只會一直重試活動直到完成。

  • 指數重試策略會以指數方式增加重試嘗試之間的時間間隔,直到活動完成或程序達到指定的停止點,例如嘗試次數的上限。

  • 自訂重試策略決定是否以及如何在每次嘗試失敗後重試活動。

以下各節會說明如何實作這些策略。範例工作流程工作者全都使用單一活動 unreliableActivity,隨機執行下列作業之一:

  • 立即完成

  • 超過逾時值故意失敗

  • 拋出 IllegalStateException 故意失敗

重試到成功為止策略

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

  1. 實作您工作流程進入點方法的 TryCatchTryCatchFinally 類別。

  2. doTry 中執行活動

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

  4. 重複步驟 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> 物件的值。

  2. runUnreliableActivityTillSuccess 實作匿名的巢狀 TryCatch 類別,以處理 unreliableActivity 活動拋出的任何例外狀況。如需深入討論如何處理匿名程式碼拋出的例外狀況,請參閱「錯誤處理」。

  3. doTry 執行 unreliableActivity 活動,這樣會傳回 Promise<Void> 物件,名為 activityRanSuccessfully

  4. doTry 呼叫非同步的 setRetryActivityToFalse 方法,它有兩個參數:

    • activityRanSuccessfully 會採用 unreliableActivity 活動傳回的 Promise<Void> 物件。

    • retryActivity 採用 retryActivity 物件。

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

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

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

  7. retryActivity 就緒時,restartRunUnreliableActivityTillSuccess 會擷取值。

    • 如果該值為 false,表示重試成功。restartRunUnreliableActivityTillSuccess 不執行任何動作,且重試序列會終止。

    • 如果值為 true,表示重試失敗。restartRunUnreliableActivityTillSuccess 會呼叫 runUnreliableActivityTillSuccess 以再次執行活動。

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

注意

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

指數重試策略

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

框架提供三種方式實作指數重試策略:

  • @ExponentialRetry 註釋是最簡單的方法,但您必須在編譯階段設定重試組態選項。

  • RetryDecorator 類別可讓您在執行時間設定重試組態,並視需要予以變更。

  • AsyncRetryingExecutor 類別可讓您在執行時間設定重試組態,並視需要予以變更。此外,框架會呼叫使用者實作的 AsyncRunnable.run 方法,執行每次的重試嘗試。

所有方法都支援下列組態選項,它們的時間值都是以秒計:

  • 初始重試等待時間。

  • 用來計算重試間隔的退避係數,如下所示:

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

    預設值為 2.0。

  • 重試嘗試次數的上限。預設值無限制。

  • 重試間隔上限。預設值無限制。

  • 過期時間。當程序期間總計超過此值時就會停止重試嘗試。預設值無限制。

  • 會觸發重試程序的例外狀況。根據預設,每種例外狀況都會觸發重試程序。

  • 不會觸發重試嘗試的例外狀況。根據預設,不排除任何例外狀況。

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

使用 @ExponentialRetry 的指數重試

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

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

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

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 方法。

  2. handleUnreliableActivity 執行 unreliableActivity 活動。

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

使用 RetryDecorator 類別的指數重試

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

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

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

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

  4. 執行活動。

如果活動失敗,框架會根據 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 物件。

  2. process 建立名為 retryDecoratorRetryDecorator 物件,並將步驟 1 中的 ExponentialRetryPolicy 物件傳遞到建構函數。

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

  4. handleUnreliableActivity 執行活動。

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

注意

ExponentialRetryPolicy 類別的數個 with 方法有對應的 set 方法,您可隨時呼叫以修改對應的組態選項:setBackoffCoefficientsetMaximumAttemptssetMaximumRetryIntervalSecondssetMaximumRetryExpirationIntervalSeconds

使用 AsyncRetryingExecutor 類別的指數重試

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

基本模式是:

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

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

  3. 實作匿名的巢狀 TryCatchTryCatchFinally 類別。

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

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

  6. 如果活動失敗,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 方法,並將組態設定傳遞給它。

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

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

  4. handleUnreliableActivity 實作匿名的巢狀 TryCatch 類別,並覆寫 doTrydoCatch 方法來執行重試嘗試及處理任何例外狀況。

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

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

  7. 如果活動失敗,執行器會根據 retryPolicy 物件組態再次呼叫 run

如需深入討論如何使用 TryCatch 類別處理錯誤,請參閱「AWS Flow Frameworkin Java 例外狀況」。

自訂重試策略

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

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

  2. 實作巢狀的 TryCatchTryCatchFinally 類別。

  3. doTry 執行活動。

  4. 如果活動失敗,doCatch 會設定狀態物件指出活動失敗。

  5. 呼叫非同步的錯誤處理方法,並將狀態物件傳遞給它。此方法會延遲例外狀況直到 TryCatchTryCatchFinally 完成。

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

以下工作流程示範如何實作自訂的重試策略。它會和 DecoratorRetryWorkflowAsyncExecutorRetryWorkflow 工作流程使用相同的 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 方法。

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

  3. callActivityWithRetry 實作匿名的巢狀 TryCatchFinally 類別,以處理 unreliableActivity 拋出的任何例外狀況。如需深入討論如何處理匿名程式碼拋出的例外狀況,請參閱「AWS Flow Frameworkin Java 例外狀況」。

  4. doTry 執行 unreliableActivity

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

  6. doFinally 檢查 failure 是否就緒,只有當 failure 是由 doCatch 所設定時才為 true。

    • 如果 failure 已就緒,則 doFinally 不執行任何動作。

    • 如果 failure 尚未就緒,活動完成且 doFinally 將錯誤設為 null

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

  8. retryOnFailure 從錯誤取得值。

    • 如果錯誤設成 null,重試嘗試就會成功。retryOnFailure 不執行任何動作,這會終止重試程序。

    • 如果錯誤設成例外狀況物件且 shouldRetry 傳回 true,retryOnFailure 會呼叫 callActivityWithRetry 重試活動。

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

  9. 步驟 2 至 8 重複,直到unreliableActivity完成或shouldRetry決定停止程序。

注意

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