Ripetere le attività non andate a buon fine - AWS Flow Framework per Java

Le traduzioni sono generate tramite traduzione automatica. In caso di conflitto tra il contenuto di una traduzione e la versione originale in Inglese, quest'ultima prevarrà.

Ripetere le attività non andate a buon fine

A volte le attività non vanno a buon fine per ragioni effimere, ad esempio una perdita temporanea della connessione. In altri casi l'attività va a buon fine, quindi il modo corretto di gestire l'errore è spesso quello di ripetere l'attività, anche più volte.

Esiste una serie di strategie per ripetere le attività; la migliore dipende dai dettagli del flusso di lavoro. Tali strategie rientrano in tre categorie di base:

  • La strategia di ripetizione fino al buon esito continua semplicemente a ripetere l'attività fino al suo completamento.

  • La strategia di ripetizione esponenziale aumenta esponenzialmente l'intervallo di tempo tra i tentativi fino al completamento dell'attività o fino a quando il processo raggiunge un punto di arresto specifico, come un numero massimo di tentativi.

  • La strategia di ripetizione personalizzata decide se o come ripetere l'attività dopo ciascun tentativo non andato a buon fine.

Le sezioni seguenti descrivono come implementare queste strategie. I lavoratori del flusso di lavoro di esempio utilizzano tutti una singola attività, unreliableActivity, che esegue casualmente una delle seguenti operazioni:

  • Viene completata immediatamente

  • Non va a buon fine intenzionalmente superando il valore di timeout

  • Non va a buon fine intenzionalmente generando IllegalStateException

Strategia di ripetizione fino al buon esito

La strategia più semplice è quella di ripetere l'attività ogni volta che ha esito negativo fino al buon esito. Il modello di base è:

  1. Implementare una classe nidificata TryCatch o TryCatchFinally nel metodo del punto di ingresso del flusso di lavoro.

  2. Eseguire l'attività in doTry.

  3. Se l'attività non va a buon fine, il framework chiama doCatch, che esegue nuovamente il metodo del punto di ingresso.

  4. Ripetere le fasi 2 e 3 fino al completamento con esito positivo dell'attività.

Il flusso di lavoro seguente implementa la strategia di ripetizione fino all'esito positivo. L'interfaccia del flusso di lavoro è implementata in RetryActivityRecipeWorkflow e ha un metodo, runUnreliableActivityTillSuccess, che è il punto di ingresso del flusso di lavoro. Il lavoratore del flusso di lavoro viene implementato in RetryActivityRecipeWorkflowImpl, nel seguente modo:

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(); } } }

Il flusso di lavoro funziona come segue:

  1. runUnreliableActivityTillSuccess crea un oggetto Settable<Boolean> denominato retryActivity che viene usato per indicare se l'attività non è riuscita e deve essere ritentata. Settable<T> è derivato da Promise<T> e funziona allo stesso modo ma il valore dell'oggetto Settable<T> viene impostato manualmente.

  2. runUnreliableActivityTillSuccess implementa una classe annidata anonima TryCatch per gestire le eccezioni generate dall'attività unreliableActivity. Per ulteriori discussioni su come gestire le eccezioni generate da un codice asincrono, consulta Gestione errori.

  3. doTry esegue l'attività unreliableActivity, che restituisce un oggetto Promise<Void> di nome activityRanSuccessfully.

  4. doTry chiama il metodo asincrono setRetryActivityToFalse, che ha due parametri:

    • activityRanSuccessfully accetta l'oggetto Promise<Void> restituito dall'attività unreliableActivity.

    • retryActivity accetta l'oggetto retryActivity.

    Se unreliableActivity viene completato, activityRanSuccessfully diventa pronto e setRetryActivityToFalse imposta retryActivity su false. In caso contrario, activityRanSuccessfully non diventa mai pronto e setRetryActivityToFalse non viene eseguito.

  5. Se unreliableActivity genera un'eccezione, il framework chiama doCatch e lo trasferisce all'oggetto dell'eccezione. doCatch imposta retryActivity su true.

  6. runUnreliableActivityTillSuccess chiama il metodo asincrono restartRunUnreliableActivityTillSuccess e lo trasferisce all'oggetto retryActivity. Poiché retryActivity è un tipo Promise<T>, restartRunUnreliableActivityTillSuccess ritarda l'esecuzione fin quando retryActivity è pronto, il che si verifica dopo il completamento di TryCatch.

  7. Quando retryActivity è pronto, restartRunUnreliableActivityTillSuccess estrae il valore.

    • Se il valore è false, il nuovo tentativo è andato a buon fine. restartRunUnreliableActivityTillSuccess non è operativo e la sequenza di ripetizione termina.

    • Se il valore è true, il nuovo tentativo non è andato a buon fine. restartRunUnreliableActivityTillSuccess chiama runUnreliableActivityTillSuccess per eseguire nuovamente l'attività.

  8. Si ripetono le fasi 1-7 fino al completamento di unreliableActivity.

Nota

doCatch non gestisce l'eccezione; imposta semplicemente l'oggetto retryActivity su true per indicare l'esito negativo dell'attività. La ripetizione è gestita dal metodo asincrono restartRunUnreliableActivityTillSuccess, che ritarda l'esecuzione fino al completamento di TryCatch. Il motivo di questo approccio è che se riprovi un'attività in doCatch non puoi annullarla. Ripetere l'attività in restartRunUnreliableActivityTillSuccess ti permette di eseguire attività annullabili.

Strategia di ripetizione esponenziale

Con la strategia di ripetizione esponenziale, il framework esegue nuovamente un'attività non andata a buon fine dopo un periodo di tempo specifico, N secondi. Se il tentativo ha esito negativo, il framework esegue nuovamente l'attività dopo 2N secondi, 4N secondi e così via. Poiché il tempo di attesa può essere lungo, in genere i tentativi si arrestano a un certo punto invece che proseguire all'infinito.

Il framework prevede tre modi per implementare una strategia di ripetizione esponenziale:

  • L'annotazione @ExponentialRetry è l'approccio più semplice, ma devi impostare le opzioni di configurazione della ripetizione al momento della compilazione.

  • La classe RetryDecorator ti permette di impostare la configurazione della ripetizione in fase di runtime e di modificarla in base alle necessità.

  • La classe AsyncRetryingExecutor ti permette di impostare la configurazione della ripetizione in fase di runtime e di modificarla in base alle necessità. Inoltre, il framework chiama un metodo AsyncRunnable.run implementato dall'utente per eseguire ogni tentativo di ripetizione.

Tutti gli approcci supportano le seguenti opzioni di configurazione, in cui i valori di tempo sono espressi in secondi:

  • Il tempo di attesa per la ripetizione iniziale.

  • Il coefficiente di backoff, che viene utilizzato per calcolare gli intervalli di ripetizione, nel modo seguente:

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

    Il valore predefinito è 2.0.

  • Il numero massimo di tentativi di ripetizione. Il valore predefinito è illimitato.

  • L'intervallo massimo di ripetizione. Il valore predefinito è illimitato.

  • Il tempo di scadenza. I tentativi si arrestano quando la durata totale del processo supera questo valore. Il valore predefinito è illimitato.

  • Le eccezioni che attivano il processo di ripetizione. Per impostazione predefinita, tutte le eccezioni attivano il processo di ripetizione.

  • Le eccezioni che non attivano tentativi di ripetizione. Per impostazione predefinita, non è esclusa alcuna eccezione.

Le sezioni seguenti descrivono i vari modi in cui è possibile implementare una strategia di ripetizione esponenziale.

Ripetizione esponenziale con @ExponentialRetry

Il modo più semplice per implementare una strategia di ripetizione esponenziale per un'attività è applicare un'annotazione @ExponentialRetry all'attività nella definizione dell'interfaccia. Se l'attività non va a buon fine, il framework gestisce automaticamente il processo di ripetizione in base ai valori opzionali specificati. Il modello di base è:

  1. Applica @ExponentialRetry alle attività in questione e specifica la configurazione di ripetizione.

  2. Se l'attività annotata non va a buon fine, il framework la recupera automaticamente secondo la configurazione specificata dagli argomenti dell'annotazione.

Il lavoratore del flusso di lavoro ExponentialRetryAnnotationWorkflow implementa la strategia di ripetizione esponenziale utilizzando un'annotazione @ExponentialRetry. Utilizza un'attività unreliableActivity la cui definizione dell'interfaccia è implementata in ExponentialRetryAnnotationActivities, nel modo seguente:

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

Le opzioni di @ExponentialRetry specificano la seguente strategia:

  • Ripeti sono se l'attività genera IllegalStateException.

  • Utilizza un tempo di attesa iniziale di 5 secondi.

  • Non più di 5 tentativi di ripetizione.

L'interfaccia del flusso di lavoro è implementata in RetryWorkflow e ha un metodo, process, che è il punto di ingresso del flusso di lavoro. Il lavoratore del flusso di lavoro viene implementato in ExponentialRetryAnnotationWorkflowImpl, nel seguente modo:

public class ExponentialRetryAnnotationWorkflowImpl implements RetryWorkflow { public void process() { handleUnreliableActivity(); } public void handleUnreliableActivity() { client.unreliableActivity(); } }

Il flusso di lavoro funziona come segue:

  1. process esegue il metodo asincrono handleUnreliableActivity.

  2. handleUnreliableActivity esegue l'attività unreliableActivity.

Se l'attività non va a buon fine generando IllegalStateException, il framework esegue automaticamente la strategia di ripetizione specificata in ExponentialRetryAnnotationActivities.

Ripetizione esponenziale con la classe RetryDecorator

@ExponentialRetry è semplice da usare. Tuttavia, la configurazione è statica e impostata al momento della compilazione, in modo che il framework utilizzi la stessa strategia di ripetizione ogni volta che l'attività non va a buon fine. Puoi implementare una strategia di ripetizione esponenziale più flessibile utilizzando la classe RetryDecorator, che ti permette di specificare la configurazione in fase di runtime e di modificarla in base alle necessità. Il modello di base è:

  1. Crea e configura un oggetto ExponentialRetryPolicy che specifichi la configurazione della ripetizione.

  2. Crea un oggetto RetryDecorator e trasferisci l'oggetto ExponentialRetryPolicy della Fase 1 al costruttore.

  3. Applica l'oggetto decorator all'attività trasferendo il nome della classe del client di attività al metodo decorato dell'oggetto RetryDecorator.

  4. Esegui l'attività.

Se l'attività non va a buon fine, il framework la ripete secondo la configurazione dell'oggetto ExponentialRetryPolicy. Puoi modificare la configurazione della ripetizione in base alla necessità cambiando l'oggetto.

Nota

L'annotazione @ExponentialRetry e la classe RetryDecorator sono reciprocamente esclusive. Non puoi utilizzare RetryDecorator per sovrascrivere dinamicamente una policy di ripetizione specificata da un'annotazione @ExponentialRetry.

La seguente implementazione del flusso di lavoro mostra come utilizzare la classe RetryDecorator per implementare una strategia di ripetizione esponenziale. Utilizza un'attività unreliableActivity priva dell'annotazione @ExponentialRetry. L'interfaccia del flusso di lavoro è implementata in RetryWorkflow e ha un metodo, process, che è il punto di ingresso del flusso di lavoro. Il lavoratore del flusso di lavoro viene implementato in DecoratorRetryWorkflowImpl, nel seguente modo:

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(); } }

Il flusso di lavoro funziona come segue:

  1. process crea e configura un oggetto ExponentialRetryPolicy nel seguente modo:

    • Trasferendo al costruttore l'intervallo di ripetizione iniziale.

    • Chiamando il metodo withMaximumAttempts dell'oggetto per impostare il numero massimo di tentativi a 5. ExponentialRetryPolicy espone altri oggetti with che è possibile usare per specificare altre opzioni di configurazione.

  2. process crea un oggetto RetryDecorator con nome retryDecorator e trasferisce l'oggetto ExponentialRetryPolicy della Fase 1 al costruttore.

  3. process applica l'elemento decorator all'attività chiamando il metodo retryDecorator.decorate e trasferendolo al nome della classe del client di attività.

  4. handleUnreliableActivity esegue l'attività.

Se l'attività non va a buon fine, il framework la ripete secondo la configurazione specificata nella Fase 1.

Nota

Molti dei metodi with della classe ExponentialRetryPolicy hanno un metodo corrispondente set che puoi chiamare per modificare l'opzione di configurazione corrispondente in qualsiasi momento: setBackoffCoefficient, setMaximumAttempts, setMaximumRetryIntervalSeconds e setMaximumRetryExpirationIntervalSeconds.

Ripetizione esponenziale con la classe AsyncRetryingExecutor

La classe RetryDecorator offre più flessibilità nella configurazione del processo di ripetizione rispetto a @ExponentialRetry, ma il framework esegue comunque automaticamente i tentativi di ripetizione, in base alla attuale configurazione dell'oggetto ExponentialRetryPolicy. Un approccio più flessibile prevede l'utilizzo della classe AsyncRetryingExecutor. Oltre a permetterti di configurare il processo di ripetizione in fase di runtime, il framework chiama un metodo AsyncRunnable.run implementato dall'utente per eseguire ogni tentativo di ripetizione invece che eseguire semplicemente l'attività.

Il modello di base è:

  1. Crea e configura un oggetto ExponentialRetryPolicy per specificare la configurazione della ripetizione.

  2. Crea un oggetto AsyncRetryingExecutor e trasferiscigli l'oggetto ExponentialRetryPolicy e un'istanza dell'orologio del flusso di lavoro.

  3. Implementa una classe annidata anonima TryCatch o TryCatchFinally.

  4. Implementa una classe anonima AsyncRunnable e sovrascrivi il metodo run per implementare il codice personalizzato per eseguire l'attività.

  5. Sovrascrivi doTry per chiamare il metodo execute dell'oggetto AsyncRetryingExecutor e trasferirlo alla classe AsyncRunnable dalla fase 4. L'oggetto AsyncRetryingExecutor chiama AsyncRunnable.run per eseguire l'attività.

  6. Se l'attività non va a buon fine, l'oggetto AsyncRetryingExecutor chiama nuovamente il metodo AsyncRunnable.run secondo la policy di ripetizione specificata nella Fase 1.

Il flusso di lavoro seguente mostra come utilizzare la classe AsyncRetryingExecutor per implementare una strategia di ripetizione esponenziale. Utilizza la stessa attività unreliableActivity del flusso di lavoro DecoratorRetryWorkflow discusso in precedenza. L'interfaccia del flusso di lavoro è implementata in RetryWorkflow e ha un metodo, process, che è il punto di ingresso del flusso di lavoro. Il lavoratore del flusso di lavoro viene implementato in AsyncExecutorRetryWorkflowImpl, nel seguente modo:

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 { } }; } }

Il flusso di lavoro funziona come segue:

  1. process chiama il metodo handleUnreliableActivity e lo trasferisce alle impostazioni della configurazione.

  2. handleUnreliableActivity utilizza le impostazioni di configurazione della Fase 1 per creare un oggetto ExponentialRetryPolicy, l'oggetto retryPolicy.

  3. handleUnreliableActivity crea un oggetto AsyncRetryExecutor, executor e trasferisce l'oggetto ExponentialRetryPolicy della Fase 2 e un'istanza dell'orologio del flusso di lavoro al costruttore

  4. handleUnreliableActivity implementa una classe annidata anonima TryCatch e sovrascrive i metodi doTry e doCatch per eseguire i tentativi di ripetizione e gestire le eventuali eccezioni.

  5. doTry crea una classe anonima AsyncRunnable e sovrascrive il metodo run per implementare il codice personalizzato per eseguire unreliableActivity. Per semplicità, run esegue semplicemente l'attività, ma puoi implementare approcci più sofisticati in base alle necessità.

  6. doTry chiama executor.execute e lo trasferisce all'oggetto AsyncRunnable. execute chiama il metodo AsyncRunnable dell'oggetto run per eseguire l'attività.

  7. Se l'attività non va a buon fine, l'esecutore chiama di nuovo run in base alla configurazione dell'oggetto retryPolicy.

Per ulteriori discussioni su come utilizzare la classe TryCatch per gestire gli errori, consulta AWS Flow Frameworkper le eccezioni Java.

Strategia di ripetizione personalizzata

L'approccio più flessibile per ripetere le attività non andate a buon fine è una strategia personalizzata, che chiama ricorsivamente un metodo asincrono che esegue il tentativo di ripetizione, più o meno come la strategia di ripetizione fino all'esito positivo. Tuttavia, invece che rieseguire semplicemente l'attività, implementi una logica personalizzata che decide se e come eseguire i successivi tentativi di ripetizione. Il modello di base è:

  1. Crea un oggetto di stato Settable<T>, che viene utilizzato per indicare se l'attività non è andata a buon fine.

  2. Implementa una classe annidata TryCatch o TryCatchFinally.

  3. doTry esegue l'attività.

  4. Se l'attività non va a buon fine, doCatch imposta l'oggetto di stato per indicare che l'attività ha avuto esito negativo.

  5. Chiama un metodo asincrono di gestione dell'errore e trasferiscilo all'oggetto di stato. Il metodo ritarda l'esecuzione fino al completamento di TryCatch o TryCatchFinally.

  6. Il metodo di gestione dell'errore decide se e quando ripetere l'attività.

Il flusso di lavoro seguente mostra come implementare una strategia di ripetizione personalizzata. Utilizza la stessa attività unreliableActivity dei flussi di lavoro DecoratorRetryWorkflow e AsyncExecutorRetryWorkflow. L'interfaccia del flusso di lavoro è implementata in RetryWorkflow e ha un metodo, process, che è il punto di ingresso del flusso di lavoro. Il lavoratore del flusso di lavoro viene implementato in CustomLogicRetryWorkflowImpl, nel seguente modo:

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

Il flusso di lavoro funziona come segue:

  1. process chiama il metodo asincrono callActivityWithRetry.

  2. callActivityWithRetry crea un errore di oggetto Settable<Throwable> denominato che viene utilizzato per indicare se l'attività non è andata a buon fine. Settable<T> deriva da Promise<T> e funziona quasi allo stesso modo, ma il valore dell'oggetto Settable<T> è impostato manualmente.

  3. callActivityWithRetry implementa una classe annidata anonima TryCatchFinally per gestire le eccezioni generate da unreliableActivity. Per ulteriori discussioni su come gestire le eccezioni generate da un codice asincrono, consulta AWS Flow Frameworkper le eccezioni Java.

  4. doTry esegue unreliableActivity.

  5. Se unreliableActivity genera un'eccezione, il framework chiama doCatch e lo trasferisce all'oggetto dell'eccezione. doCatch imposta failure sull'oggetto dell'eccezione, il che indica che l'attività non è andata a buon fine e mette l'oggetto in stato di pronto.

  6. doFinally verifica se failure è pronto, che sarà vero solo se failure è stato impostato da doCatch.

    • Se failure è pronto, doFinally non fa alcunché.

    • Se failure non è pronto, l'attività viene completata e doFinally imposta l'errore su null.

  7. callActivityWithRetry chiama il metodo asincrono retryOnFailure e vi trasferisce l'errore. Poiché l'errore è un tipo Settable<T>, callActivityWithRetry ritarda l'esecuzione fin quando l'errore è pronto, il che si verifica dopo il completamento di TryCatchFinally.

  8. retryOnFailure riceve il valore dall'errore.

    • Se l'errore è impostato su null, il tentativo di ripetizione è andato a buon fine. retryOnFailure non fa alcunché, il che termina il processo di ripetizione.

    • Se l'errore è impostato su un oggetto di eccezione e shouldRetry restituisce il valore true, retryOnFailure chiama callActivityWithRetry per riprovare l'attività.

      shouldRetry implementa una logica personalizzata per decidere se ripetere un'attività dall'esito negativo. Per semplicità, shouldRetry restituisce sempre il valore true e retryOnFailure esegue immediatamente l'attività, ma puoi implementare una logica più sofisticata in base alle necessità.

  9. I passaggi 2—8 ripetono fino a quandounreliableActivitycompleta oshouldRetrydecide di interrompere il processo.

Nota

doCatch non gestisce il processo di ripetizione; imposta semplicemente l'errore per indicare l'esito negativo dell'attività. Il processo di ripetizione è gestito dal metodo asincrono retryOnFailure, che ritarda l'esecuzione fino al completamento di TryCatch. Il motivo di questo approccio è che se riprovi un'attività in doCatch non puoi annullarla. Ripetere l'attività in retryOnFailure ti permette di eseguire attività annullabili.