Tentar novamente atividades com falha - AWS Flow Framework para Java

As traduções são geradas por tradução automática. Em caso de conflito entre o conteúdo da tradução e da versão original em inglês, a versão em inglês prevalecerá.

Tentar novamente atividades com falha

As atividades falham às vezes por motivos efêmeros, como uma perda temporária de conectividade. Em outro momento, a atividade pode funcionar, assim a forma apropriada de lidar com falhas de atividade geralmente é repetir a atividade, talvez algumas vezes.

Existe uma variedade de estratégias para repetir atividades. A melhor depende dos detalhes do fluxo de trabalho. As estratégias são divididas em três categorias básicas:

  • A estratégia de repetir até o sucesso simplesmente repete a atividade até ela ser concluída.

  • A estratégia de repetição exponencial aumenta o intervalo de tempo entre as tentativas exponencialmente até que a atividade seja concluída ou o processo alcançar um ponto de parada especificado, como um número máximo de tentativas.

  • A estratégia de repetição personalizada decide se ou como repetir a atividade após cada tentativa falha.

As seções a seguir descrevem como implementar essas estratégias. Todos os operadores de fluxo de trabalho de exemplo utilizam uma única atividade, unreliableActivity, que realiza de forma aleatória um dos seguintes:

  • Conclui imediatamente

  • Falha intencionalmente ao exceder o valor do tempo limite

  • Falha intencionalmente lançando uma IllegalStateException

Estratégia de repetir até o sucesso

A estratégia mais simples de repetição é continuar repetindo a atividade até que eventualmente seja concluída. O padrão básico é:

  1. Implementar uma classe aninhada TryCatch ou TryCatchFinally no método do ponto de entrada do fluxo de trabalho.

  2. Executar a atividade em doTry

  3. Se a atividade falhar, a estrutura chama doCatch, que executa o método do ponto de entrada novamente.

  4. Repetir as Etapas 2 e 3 até que a atividade seja concluída com êxito.

O fluxo de trabalho a seguir implementa a estratégia de repetir até o sucesso. A interface do fluxo de trabalho é implementada no RetryActivityRecipeWorkflow e possui um método, runUnreliableActivityTillSuccess, que é o ponto de entrada do fluxo de trabalho. O operador do fluxo de trabalho é implementado em RetryActivityRecipeWorkflowImpl, da seguinte maneira:

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

O fluxo de trabalho funciona da seguinte forma:

  1. runUnreliableActivityTillSuccess cria um objeto Settable<Boolean> chamado retryActivity que é usado para indicar se a atividade falhou e deve ser executada novamente. Settable<T> é derivado do Promise<T> e funciona da mesma forma, mas você define o valor de um objeto Settable<T> manualmente.

  2. runUnreliableActivityTillSuccess implementa uma classe aninhada TryCatch anônima para processar todas as exceções lançadas pela atividade unreliableActivity. Para obter mais discussões sobre como gerenciar as exceções lançadas pelo código assíncrono, consulte Como tratar erros.

  3. doTry executa a atividade unreliableActivity, que retorna um objeto Promise<Void> chamado activityRanSuccessfully.

  4. doTry chama o método assíncrono setRetryActivityToFalse, que possui dois parâmetros:

    • activityRanSuccessfully recebe o objeto Promise<Void> retornado pela atividade unreliableActivity.

    • retryActivity recebe o objeto retryActivity.

    Se a unreliableActivity for concluída, activityRanSuccessfully se torna pronto e setRetryActivityToFalse define retryActivity como falso. Caso contrário, activityRanSuccessfully nunca se torna pronto e setRetryActivityToFalse não é executado.

  5. Se a unreliableActivity lança uma exceção, a estrutura chama doCatch e envia-lhe o objeto de exceção. doCatch define retryActivity como verdadeiro.

  6. runUnreliableActivityTillSuccess chama o método assíncrono restartRunUnreliableActivityTillSuccess e envia-lhe o objeto retryActivity. Como retryActivity é do tipo Promise<T>, restartRunUnreliableActivityTillSuccess adia a execução até que a retryActivity esteja pronta, o que ocorre após TryCatch concluir.

  7. Quando a retryActivity estiver pronta, restartRunUnreliableActivityTillSuccess extrai o valor.

    • Se o valor for false, a nova tentativa foi bem-sucedida. restartRunUnreliableActivityTillSuccess não faz nada e a sequência de repetição é encerrada.

    • Se o valor for verdadeiro, a nova tentativa falhou. restartRunUnreliableActivityTillSuccess chama runUnreliableActivityTillSuccess para executar a atividade novamente.

  8. As Etapas 1 a 7 são repetias até que a unreliableActivity seja concluída.

nota

doCatch não lida com a exceção, ele simplesmente define o objeto retryActivity como verdadeiro para indicar que a atividade falhou. A repetição é gerenciada pelo método assíncrono restartRunUnreliableActivityTillSuccess, que adia a execução até que TryCatch seja concluído. O motivo desta abordagem é que, se você repetir uma atividade no doCatch, não é possível cancelá-la. Repetir a atividade em restartRunUnreliableActivityTillSuccess permite executar atividades canceláveis.

Estratégia de repetição exponencial

Com a estratégia de repetição exponencial, a estrutura executa uma atividade falha novamente após um período especificado, N segundos. Se essa tentativa falhar, a estrutura executa a atividade novamente após 2N segundos e, em seguida, 4N segundos e assim por diante. Como o tempo de espera pode se tornar grande, normalmente você interrompe as tentativas de repetição em algum momento em vez de continuar indefinidamente.

A estrutura oferece três maneiras para implementar uma estratégia de repetição exponencial:

  • A anotação @ExponentialRetry é a abordagem mais simples, mas você deve definir as opções de configuração da repetição no momento da compilação.

  • A classe RetryDecorator permite definir a configuração de repetição durante o tempo de execução e alterá-la conforme necessário.

  • A classe AsyncRetryingExecutor permite definir a configuração de repetição durante o tempo de execução e alterá-la conforme necessário. Além disso, a estrutura chama um método AsyncRunnable.run implementado pelo usuário para executar cada tentativa de repetição.

Todas as abordagens oferecem suporte para as seguintes opções de configuração, onde os valores de tempo estão em segundos:

  • O tempo de espera da repetição inicial.

  • O coeficiente de recuo, que é usado para computar os intervalos de repetição, da seguinte forma:

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

    O valor padrão é 2.0.

  • O número máximo de tentativas de repetição. O valor padrão é ilimitado.

  • O intervalo máximo de repetição. O valor padrão é ilimitado.

  • O tempo de expiração. As tentativas de repetição param quando a duração total do processo excede esse valor. O valor padrão é ilimitado.

  • As exceções que acionarão o processo de repetição. Por padrão, toda exceção aciona o processo de repetição.

  • As exceções que não acionarão um processo de repetição. Por padrão, nenhuma exceção está excluída.

As seções a seguir descrevem as diversas formas para implementar uma estratégia de repetição exponencial.

Repetição exponencial com @ExponentialRetry

A forma mais simples de implementar uma estratégia de repetição exponencial para uma atividade é aplicar uma anotação @ExponentialRetry à atividade na definição da interface. Se a atividade falhar, a estrutura gerencia o processo de repetição automaticamente, com base nos valores da opção especificada. O padrão básico é:

  1. Aplique @ExponentialRetry às atividades apropriadas e especifique a configuração de repetição.

  2. Se uma atividade anotada falhar, a estrutura repete automaticamente a atividade de acordo com a configuração especificada pelos argumentos da anotação.

O operador de fluxo de trabalho ExponentialRetryAnnotationWorkflow implementa a estratégia de repetição exponencial usando uma anotação @ExponentialRetry. Ele usa uma atividade unreliableActivity, cuja definição de interface é implementada nas ExponentialRetryAnnotationActivities, da seguinte forma:

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

As opções @ExponentialRetry especificam a seguinte estratégia:

  • Repetir somente se a atividade lançar uma IllegalStateException.

  • Usar um tempo de espera inicial de 5 segundos.

  • Não mais que cinco tentativas de repetição.

A interface do fluxo de trabalho é implementada no RetryWorkflow e possui um método, process, que é o ponto de entrada do fluxo de trabalho. O operador do fluxo de trabalho é implementado em ExponentialRetryAnnotationWorkflowImpl, da seguinte maneira:

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

O fluxo de trabalho funciona da seguinte forma:

  1. process executa o método síncrono handleUnreliableActivity.

  2. handleUnreliableActivity executa a atividade unreliableActivity.

Se a atividade falhar ao lançar uma IllegalStateException, a estrutura executa automaticamente a estratégia de repetição especificada nas ExponentialRetryAnnotationActivities.

Repetição exponencial com a classe RetryDecorator

O uso do @ExponentialRetry é simples. No entanto, a configuração é estática e definida durante a compilação, portanto a estrutura usa a mesma estratégia de repetição sempre que a atividade falhar. É possível implementar uma estratégia de repetição exponencial mais flexível usando a classe RetryDecorator, que permite especificar a configuração durante o tempo de execução e alterá-la conforme necessário. O padrão básico é:

  1. Crie e configure um objeto ExponentialRetryPolicy que especifica a configuração de repetição.

  2. Crie um objeto RetryDecorator e envie o objeto ExponentialRetryPolicy da Etapa 1 para o construtor.

  3. Aplique o objeto decorador à atividade enviando o nome de classe do cliente da atividade para o método de decoração do objeto RetryDecorator.

  4. Execute a atividade.

Se a atividade falhar, a estrutura repete a atividade de acordo com a configuração do objeto ExponentialRetryPolicy. Altere a configuração de repetição conforme necessário modificando esse objeto.

nota

A anotação @ExponentialRetry e a classe RetryDecorator são mutuamente exclusivas. Não é possível usar RetryDecorator para substituir dinamicamente uma política de repetição especificada por uma anotação @ExponentialRetry.

A implementação de fluxo de trabalho a seguir mostra como usar a classe RetryDecorator para implementar uma estratégia de repetição exponencial. Ela usa uma atividade unreliableActivity que não possui uma anotação @ExponentialRetry. A interface do fluxo de trabalho é implementada no RetryWorkflow e possui um método, process, que é o ponto de entrada do fluxo de trabalho. O operador do fluxo de trabalho é implementado em DecoratorRetryWorkflowImpl, da seguinte maneira:

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

O fluxo de trabalho funciona da seguinte forma:

  1. process cria e configura um objeto ExponentialRetryPolicy ao:

    • Enviar o intervalo inicial de repetição ao construtor.

    • Chamar o método withMaximumAttempts do objeto para definir o número máximo de tentativas como cinco. ExponentialRetryPolicy expõe outros objetos with que você pode usar para especificar outras opções de configuração.

  2. process cria um objeto RetryDecorator chamado retryDecorator e envia o objeto ExponentialRetryPolicy da Etapa 1 ao construtor.

  3. process aplica o decorador à atividade chamando o método retryDecorator.decorate e enviando-lhe o nome de classe do cliente da atividade.

  4. handleUnreliableActivity executa a atividade.

Se a atividade falhar, a estrutura repete-a de acordo com a configuração especificada na Etapa 1.

nota

Muitos dos métodos with da classe ExponentialRetryPolicy possuem um método set correspondente que pode ser chamado para modificar a opção de configuração correspondente a qualquer momento: setBackoffCoefficient, setMaximumAttempts, setMaximumRetryIntervalSeconds e setMaximumRetryExpirationIntervalSeconds.

Repetição exponencial com a classe AsyncRetryingExecutor

A classe RetryDecorator oferece mais flexibilidade ao configurar o processo de repetição que a @ExponentialRetry, mas a estrutura ainda executa as tentativas de repetição automaticamente, com base na configuração atual do objeto ExponentialRetryPolicy. Uma abordagem mais flexível é usar a classe AsyncRetryingExecutor. Além de permitir que você configure o processo de repetição durante o tempo de execução, a estrutura chama um método AsyncRunnable.run implementado pelo usuário para executar cada tentativa de repetição em vez de simplesmente executar a atividade.

O padrão básico é:

  1. Crie e configure um objeto ExponentialRetryPolicy para especificar a configuração de repetição.

  2. Crie um objeto AsyncRetryingExecutor e envie-lhe o objeto ExponentialRetryPolicy e uma instância do clock do fluxo de trabalho.

  3. Implemente uma classe aninhada TryCatch ou TryCatchFinally anônima.

  4. Implemente uma classe AsyncRunnable anônima e substitua o método run para implementar o código personalizado para execução da atividade.

  5. Substitua doTry para chamar o método execute do objeto AsyncRetryingExecutor enviar-lhe a classe AsyncRunnable da Etapa 4. O objeto AsyncRetryingExecutor chama AsyncRunnable.run para executar a atividade.

  6. Se a atividade falhar, o objeto AsyncRetryingExecutor chama o método AsyncRunnable.run novamente, de acordo com a política de repetição especificada na Etapa 1.

O fluxo de trabalho a seguir mostra como usar a classe AsyncRetryingExecutor para implementar uma estratégia de repetição exponencial. Ele usa a mesma atividade unreliableActivity que o fluxo de trabalho DecoratorRetryWorkflow discutido anteriormente. A interface do fluxo de trabalho é implementada no RetryWorkflow e possui um método, process, que é o ponto de entrada do fluxo de trabalho. O operador do fluxo de trabalho é implementado em AsyncExecutorRetryWorkflowImpl, da seguinte maneira:

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

O fluxo de trabalho funciona da seguinte forma:

  1. process chama o método handleUnreliableActivity e envia-lhe as definições de configuração.

  2. handleUnreliableActivity usa as definições de configuração da Etapa 1 para criar um objeto ExponentialRetryPolicy, retryPolicy.

  3. handleUnreliableActivity cria um objeto AsyncRetryExecutor, executor, e envia o objeto ExponentialRetryPolicy da Etapa 2 e uma instância do clock do fluxo de trabalho ao construtor

  4. handleUnreliableActivity implementa uma classe aninhada TryCatch anônima e substitui os métodos doTry e doCatch para executar as tentativas de repetição e gerenciar quaisquer exceções.

  5. doTry cria uma classe AsyncRunnable anônima e substitui o método run para implementar o código personalizado para executar a unreliableActivity. Por simplicidade, run apenas executa a atividade, mas você pode implementar abordagens mais sofisticadas conforme for apropriado.

  6. doTry chama executor.execute e envia-lhe o objeto AsyncRunnable. execute chama o método AsyncRunnable do objeto run para executar a atividade.

  7. Se a atividade falhar, o executor chama run novamente, de acordo com a configuração do objeto retryPolicy.

Para obter mais discussões sobre como usar a classe TryCatch para gerenciar erros, consulte AWS Flow Framework para exceções de Java.

Estratégia de repetição personalizada

A abordagem mais flexível para repetir atividades com falha é uma estratégia personalizada, que chama recursivamente um método assíncrono que executa a tentativa de repetição, de forma bem semelhante à estratégia repetir até o sucesso. No entanto, em vez de simplesmente executar a atividade novamente, implemente a lógica personalizada que decide se e como executar cada tentativa sucessiva de repetição. O padrão básico é:

  1. Crie um objeto de status Settable<T>, que é usado para indicar se a atividade falhou.

  2. Implemente uma classe aninhada TryCatch ou TryCatchFinally.

  3. doTry executa a atividade.

  4. Se a atividade falhar, doCatch define o objeto de status para indicar que a atividade falhou.

  5. Chame um método assíncrono de gerenciamento de falhas e envie-lhe o objeto de status. O método adia a execução até que TryCatch ou TryCatchFinally conclua.

  6. O método de gerenciamento de falhas decide se deve repetir a atividade e, se sim, quando.

O fluxo de trabalho a seguir mostra como implementar uma estratégia de repetição personalizada. Ele usa a mesma atividade unreliableActivity que os fluxos de trabalho DecoratorRetryWorkflow e AsyncExecutorRetryWorkflow. A interface do fluxo de trabalho é implementada no RetryWorkflow e possui um método, process, que é o ponto de entrada do fluxo de trabalho. O operador do fluxo de trabalho é implementado em CustomLogicRetryWorkflowImpl, da seguinte maneira:

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

O fluxo de trabalho funciona da seguinte forma:

  1. process chama o método assíncrono callActivityWithRetry.

  2. callActivityWithRetry cria um objeto Settable<Throwable> chamado failure que é usado para indicar se a atividade falhou. Settable<T> é derivado de Promise<T> e funciona da mesma forma, mas você define o valor de um objeto Settable<T> manualmente.

  3. callActivityWithRetry implementa uma classe aninhada TryCatchFinally anônima para processar todas as exceções lançadas pela unreliableActivity. Para obter mais discussões sobre como gerenciar as exceções lançadas pelo código assíncrono, consulte AWS Flow Framework para exceções de Java.

  4. doTry executa a unreliableActivity.

  5. Se unreliableActivity lança uma exceção, a estrutura chama doCatch e envia-lhe o objeto de exceção. doCatch define failure para o objeto de exceção, o que indica que a atividade falhou e coloca o objeto em um estado pronto.

  6. doFinally verifica se failure está pronto, que será verdadeiro somente se failure foi definido por doCatch.

    • Se failure estiver pronto, doFinally não faz nada.

    • Se failure não estiver pronto, a atividade foi concluída e doFinally define failure como null.

  7. callActivityWithRetry chama o método assíncrono retryOnFailure e envia-lhe o failure. Como failure é do tipo Settable<T>, callActivityWithRetry adia a execução até que failure esteja pronto, o que ocorre após TryCatchFinally concluir.

  8. retryOnFailure obtém o valor de failure.

    • Se failure está definido como null, a tentativa de repetição foi bem-sucedida. retryOnFailure não faz nada, o que encerra o processo de repetição.

    • Se failure for definido como um objeto de exceção e shouldRetry retornar verdadeiro, retryOnFailure chama callActivityWithRetry para repetir a atividade.

      shouldRetry implementa a lógica personalizada para decidir se deve repetir uma atividade com falha. Por simplicidade, shouldRetry sempre retorna true e retryOnFailure executa a atividade imediatamente, mas você pode implementar uma lógica mais sofisticada conforme necessário.

  9. As etapas 2 a 8 se repetem até que unreliableActivity seja concluído ou shouldRetry decida interromper o processo.

nota

doCatch não lida com o processo de repetição, ele simplesmente define failure para indicar que a atividade falhou. A processo de repetição é gerenciado pelo método assíncrono retryOnFailure, que adia a execução até que TryCatch seja concluído. O motivo desta abordagem é que, se você repetir uma atividade no doCatch, não é possível cancelá-la. Repetir a atividade em retryOnFailure permite executar atividades canceláveis.