Reintento de actividades con errores - AWS Flow Framework para Java

Las traducciones son generadas a través de traducción automática. En caso de conflicto entre la traducción y la version original de inglés, prevalecerá la version en inglés.

Reintento de actividades con errores

En ocasiones se producen errores en las actividades por motivos efímeros, como por ejemplo la pérdida temporal de conexión. En otras ocasiones, la actividad podría tener éxito, por lo que la manera apropiada de abordar el error en la actividad consiste con frecuencia en reintentar la actividad, quizás varias veces.

Hay diferentes estrategias para reintentar actividades; la mejor depende de los detalles de su flujo de trabajo. Las estrategias se dividen en tres categorías básicas:

  • La estrategia de reintento hasta tener éxito simplemente sigue reintentando la actividad hasta que la completa.

  • La estrategia de reintento exponencial aumenta el intervalo de tiempo entre reintentos exponencialmente hasta que se completa la actividad o el proceso alcanza un punto de parada especificado, como por ejemplo un número máximo de intentos.

  • La estrategia de reintento personalizada decide si se reintenta la actividad, o cómo hacerlo, después de cada intento en el que se ha producido un error.

Las siguientes secciones describen cómo implementar estas estrategias. Todos los procesos de trabajo de flujo de trabajo de ejemplo utilizan una sola actividad, unreliableActivity, que hace lo siguiente de manera aleatoria:

  • Se completa de manera inmediata

  • Produce un error de manera intencionada superando el valor del tiempo de espera

  • Produce un error de manera intencionada produciendo una IllegalStateException

Estrategia de reintento hasta tener éxito

La estrategia de reintento más sencilla consiste en seguir reintentando la actividad cada vez que se produce un error hasta que finalmente se ejecuta satisfactoriamente. Este es el patrón básico:

  1. Implemente una clase TryCatch o TryCatchFinally anidada en el método de punto de entrada de su flujo de trabajo.

  2. Ejecute la actividad en doTry

  3. Si se produce un error en la actividad, el marco de trabajo llama a doCatch, que ejecuta de nuevo el método de punto de entrada.

  4. Repita los pasos 2 y 3 hasta que la actividad se realiza correctamente.

El siguiente flujo de trabajo implementa la estrategia de reintento hasta tener éxito. La interfaz de flujo de trabajo se implementa en RetryActivityRecipeWorkflow y tiene un método, runUnreliableActivityTillSuccess, que es el punto de entrada del flujo de trabajo. El proceso de trabajo de flujo de trabajo se implementa en RetryActivityRecipeWorkflowImpl, de la siguiente manera:

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

El flujo de trabajo funciona de la siguiente manera:

  1. runUnreliableActivityTillSuccess crea un objeto Settable<Boolean> denominado retryActivity que se utiliza para indicar si se ha producido un error en la actividad y debería volver a intentarse. Settable<T> proviene de Promise<T> y funciona de forma muy parecida, pero en este caso, usted establece manualmente el valor de un objeto Settable<T>.

  2. runUnreliableActivityTillSuccess implementa una clase TryCatch anidada de manera anónima para gestionar cualquier excepción lanzada por la actividad unreliableActivity. Para obtener más información sobre cómo tratar excepciones lanzadas por un código asíncrono, consulte Control de errores.

  3. doTry ejecuta la actividad unreliableActivity, que devuelve un objeto Promise<Void> denominado activityRanSuccessfully.

  4. doTry llama al método asíncrono setRetryActivityToFalse, que tiene dos parámetros:

    • activityRanSuccessfully toma el objeto Promise<Void> devuelto por la actividad unreliableActivity.

    • retryActivity toma el objeto retryActivity.

    Si unreliableActivity finaliza, activityRanSuccessfully está listo y setRetryActivityToFalse establece retryActivity en "false". De lo contrario, activityRanSuccessfully nunca está listo y setRetryActivityToFalse no se ejecuta.

  5. Si unreliableActivity genera una excepción, el marco de trabajo llama a doCatch y le pasa el objeto de excepción. doCatch establece retryActivity en true.

  6. runUnreliableActivityTillSuccess al método asíncrono restartRunUnreliableActivityTillSuccess y le pasa el objeto retryActivity. Dado que retryActivity es un tipo de Promise<T>, restartRunUnreliableActivityTillSuccess aplaza la ejecución hasta que retryActivity esté listo, lo que ocurre después de que se completa TryCatch.

  7. Cuando retryActivity está listo, restartRunUnreliableActivityTillSuccess extrae el valor.

    • Si el valor es false, el reintento ha tenido éxito. restartRunUnreliableActivityTillSuccess no hace nada y la secuencia de reintento termina.

    • Si el valor es "true", se ha producido un error en el reintento. restartRunUnreliableActivityTillSuccess llama a runUnreliableActivityTillSuccess para ejecutar de nuevo la actividad.

  8. Los pasos 1 al 7 se repiten hasta que se completa unreliableActivity.

nota

doCatch no gestiona la excepción; simplemente establece el objeto retryActivity en "true" para indicar que se ha producido un error en la actividad. El reintento es gestionado por el método asíncrono restartRunUnreliableActivityTillSuccess, que aplaza la ejecución hasta que se completa TryCatch. El motivo de este enfoque es que, si reintenta una actividad en doCatch, no es posible cancelarla. Volver a intentar la actividad restartRunUnreliableActivityTillSuccess le permite ejecutar actividades que se pueden cancelar.

Estrategia de reintento exponencial

Con la estrategia de reintento exponencial, el marco de trabajo ejecuta una actividad en la que se ha producido un error de nuevo tras un periodo de tiempo especificado, N segundos. Si se produce un error en ese intento, el marco de trabajo ejecuta de nuevo la actividad después de 2N segundos y luego tras 4N segundos, etc. Debido a que el tiempo de espera puede aumentar bastante, habitualmente interrumpe los reintentos en algún punto en lugar de continuar de manera indefinida.

El marco de trabajo ofrece tres maneras de implementar una estrategia de reintento exponencial:

  • La anotación @ExponentialRetry es el enfoque más sencillo, pero debe establecer las opciones de configuración de los reintentos en tiempo de compilación.

  • La clase RetryDecorator le permite establecer la configuración de reintentos en el tiempo de ejecución y cambiarla según sea necesario.

  • La clase AsyncRetryingExecutor le permite establecer la configuración de reintentos en el tiempo de ejecución y cambiarla según sea necesario. Además, el marco de trabajo llama a un método AsyncRunnable.run implementado por el usuario para la ejecución de cada reintento.

Todos los enfoques admiten las siguientes opciones de configuración, en las que los valores de tiempo se muestran en segundos:

  • El tiempo de espera de reintento inicial.

  • El coeficiente de retardo, que se utiliza para computar los intervalos de reintento, de la siguiente manera:

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

    El valor predeterminado es 2.0.

  • El número máximo de reintentos. El valor predeterminado es ilimitado.

  • El intervalo máximo de reintentos. El valor predeterminado es ilimitado.

  • El plazo de vencimiento. Los reintentos se detienen cuando la duración total del proceso supera este valor. El valor predeterminado es ilimitado.

  • Las excepciones que dispararán el proceso de reintento. De manera predeterminada, todas las excepciones disparan el proceso de reintento.

  • Las excepciones que no dispararán un reintento. De manera predeterminada, no se excluye ninguna excepción.

En las siguientes secciones se describen las distintas maneras de implementar una estrategia de reintento exponencial.

Reintento exponencial con @ExponentialRetry

La manera más sencilla de implementar una estrategia de reintento exponencial para una actividad consiste en aplicar una anotación @ExponentialRetry a la actividad en la definición de interfaz. Si se produce un error en la actividad, el marco de trabajo gestiona el proceso de reintento automáticamente, en función de los valores de opciones especificados. Este es el patrón básico:

  1. Aplique @ExponentialRetry a las actividades apropiadas y especifique la configuración de reintento.

  2. Si se produce un error en una actividad anotada, el marco de trabajo reintenta automáticamente la actividad en función de la configuración especificada por los argumentos del comentario.

El proceso de trabajo del flujo de trabajo ExponentialRetryAnnotationWorkflow implementa la estrategia de reintento exponencial utilizando una anotación @ExponentialRetry. Utiliza una actividad unreliableActivity cuya definición de interfaz se implementa en ExponentialRetryAnnotationActivities, de la siguiente manera:

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

Las opciones de @ExponentialRetry especifican la siguiente estrategia:

  • Reintentar solo si la actividad lanza IllegalStateException.

  • Usar el tiempo de espera inicial de 5 segundos.

  • No más de 5 reintentos.

La interfaz de flujo de trabajo se implementa en RetryWorkflow y tiene un método, process, que es el punto de entrada del flujo de trabajo. El proceso de trabajo de flujo de trabajo se implementa en ExponentialRetryAnnotationWorkflowImpl, de la siguiente manera:

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

El flujo de trabajo funciona de la siguiente manera:

  1. process ejecuta el método síncrono handleUnreliableActivity.

  2. handleUnreliableActivity ejecuta la actividad unreliableActivity.

Si se produce un error en la actividad y lanza IllegalStateException, el marco de trabajo ejecuta automáticamente la estrategia de reintento especificada en ExponentialRetryAnnotationActivities.

Reintento exponencial con la clase RetryDecorator

@ExponentialRetry es fácil de utilizar. No obstante, la configuración es estática y se establece en el tiempo de compilación, por lo que el marco de trabajo utiliza la misma estrategia de reintento cada vez que se produce un error en la actividad. Puede implementar una estrategia de reintento exponencial más flexible utilizando la clase RetryDecorator que le permite especificar la configuración en el tiempo de ejecución y cambiarla según sea necesario. Este es el patrón básico:

  1. Cree y configure un objeto ExponentialRetryPolicy que especifique la configuración de reintento.

  2. Cree un objeto RetryDecorator y pase el objeto ExponentialRetryPolicy del Paso 1 al constructor.

  3. Aplique el objeto decorador a la actividad pasando el nombre de la clase del cliente de la actividad al método de decoración del objeto RetryDecorator.

  4. Ejecute la actividad.

Si se produce un error en la actividad, el marco de trabajo reintenta la actividad en función de la configuración del objeto ExponentialRetryPolicy. Puede cambiar la configuración de los reintentos según sea necesario modificando este objeto.

nota

La anotación @ExponentialRetry y la clase RetryDecorator se excluyen mutuamente. No puede utilizar RetryDecorator para anular dinámicamente una política de reintentos especificada por una anotación @ExponentialRetry.

La siguiente implementación de flujo de trabajo muestra cómo usar la clase RetryDecorator para implementar una estrategia de reintento exponencial. Utiliza una actividad unreliableActivity que no tiene una anotación @ExponentialRetry. La interfaz de flujo de trabajo se implementa en RetryWorkflow y tiene un método, process, que es el punto de entrada del flujo de trabajo. El proceso de trabajo de flujo de trabajo se implementa en DecoratorRetryWorkflowImpl, de la siguiente manera:

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

El flujo de trabajo funciona de la siguiente manera:

  1. process crea y configura un objeto ExponentialRetryPolicy de la siguiente manera:

    • Pasando el intervalo de reintentos inicial al constructor.

    • Llamando al método withMaximumAttempts del objeto para establecer el número máximo de intentos en 5. ExponentialRetryPolicy expone otros objetos with que se pueden utilizar para especificar otras opciones de configuración.

  2. process crea un objeto RetryDecorator denominado retryDecorator y pasa el objeto ExponentialRetryPolicy del Paso 1 al constructor.

  3. process aplica el decorador a la actividad llamando al método retryDecorator.decorate y pasándole el nombre de la clase del cliente de la actividad.

  4. handleUnreliableActivity ejecuta la actividad.

Si se produce un error en la actividad, el marco de trabajo lo reintenta en función de la configuración especificada en el Paso 1.

nota

Varios de los métodos with de la clase ExponentialRetryPolicy tienen un método set correspondiente que puede llamar para modificar la opción de configuración correspondiente en cualquier momento: setBackoffCoefficient, setMaximumAttempts, setMaximumRetryIntervalSeconds y setMaximumRetryExpirationIntervalSeconds.

Reintento exponencial con la clase AsyncRetryingExecutor

La clase RetryDecorator ofrece más flexibilidad en la configuración del proceso de reintento que @ExponentialRetry, pero el marco de trabajo sigue ejecutando los reintentos automáticamente, en función de la configuración actual del objeto ExponentialRetryPolicy. Un enfoque más flexible consiste en usar la clase AsyncRetryingExecutor. Además de permitirle configurar el proceso de reintento en el tiempo de ejecución, el marco de trabajo llama a un método AsyncRunnable.run implementado por el usuario para que ejecute cada reintento en lugar de simplemente ejecutar la actividad.

Este es el patrón básico:

  1. Cree y configure un objeto ExponentialRetryPolicy para especificar la configuración de reintento.

  2. Cree un objeto AsyncRetryingExecutor y pásele el objeto ExponentialRetryPolicy y una instancia del reloj del flujo de trabajo.

  3. Implemente una clase TryCatch o TryCatchFinally anidada anónima.

  4. Implemente una clase AsyncRunnable anónima y anule el método run para la implementación del código personalizado para la ejecución de la actividad.

  5. Anule doTry para llamar al método execute del objeto AsyncRetryingExecutor y pasarle la clase AsyncRunnable del Paso 4. El objeto AsyncRetryingExecutor llama a AsyncRunnable.run para ejecutar la actividad.

  6. Si se produce un error en la actividad, el objeto AsyncRetryingExecutor llama de nuevo al método AsyncRunnable.run en función de la política de reintentos especificada en el Paso 1.

El siguiente flujo de trabajo muestra cómo usar la clase AsyncRetryingExecutor para implementar una estrategia de reintento exponencial. Utiliza la misma actividad unreliableActivity que el flujo de trabajo DecoratorRetryWorkflow sobre el que hemos hablado antes. La interfaz de flujo de trabajo se implementa en RetryWorkflow y tiene un método, process, que es el punto de entrada del flujo de trabajo. El proceso de trabajo de flujo de trabajo se implementa en AsyncExecutorRetryWorkflowImpl, de la siguiente manera:

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

El flujo de trabajo funciona de la siguiente manera:

  1. process llama al método handleUnreliableActivity y le pasa los ajustes de la configuración.

  2. handleUnreliableActivity utiliza los ajustes de la configuración del Paso 1 para crear un objeto ExponentialRetryPolicy, retryPolicy.

  3. handleUnreliableActivity crea un objeto AsyncRetryExecutor, executor, y pasa el objeto ExponentialRetryPolicy del Paso 2 y una instancia del reloj del flujo de trabajo al constructor.

  4. handleUnreliableActivity implementa una clase TryCatch anidada de manera anónima y anula los métodos doTry y doCatch para ejecutar los reintentos y gestionar cualquier excepción.

  5. doTry crea una clase AsyncRunnable anónima y anula el método run para la implementación del código personalizado para la ejecución de unreliableActivity. Para simplificar, run simplemente ejecuta la actividad, pero puede implementar un enfoque más sofisticado según considere apropiado.

  6. doTry llama a executor.execute y le pasa el objeto AsyncRunnable. execute llama al método run del objeto AsyncRunnable para ejecutar la actividad.

  7. Si se produce un error en la actividad, el ejecutor llama a run de nuevo, en función de la configuración del objeto retryPolicy.

Para obtener más información sobre cómo utilizar la clase TryCatch para gestionar errores, consulte Excepciones del AWS Flow Framework para Java.

Estrategia de reintento personalizada

El enfoque más flexible para el reintento de actividades en las que se ha producido un error es una estrategia personalizada, que llama de forma recursiva a un método asíncrono que ejecuta el reintento, de forma parecida a la estrategia de reintento hasta alcanzar el éxito. No obstante, en lugar de simplemente ejecutar la actividad de nuevo, usted implementa la lógica personalizada que decide si se ejecuta cada reintento sucesivo y cómo hacerlo. Este es el patrón básico:

  1. Cree un objeto de estado Settable<T> que se utiliza para indicar si se ha producido un error en la actividad.

  2. Implemente una clase TryCatch o TryCatchFinally anidada.

  3. doTry ejecuta la actividad.

  4. Si se produce un error en la actividad, doCatch establece el objeto de estado para indicar que se ha producido un error en la actividad.

  5. Llame al método asíncrono de gestión de errores y pásele el objeto de estado. El método aplaza la ejecución hasta que TryCatch o TryCatchFinally se completan.

  6. El método de gestión de errores decide si se vuelve a intentar la actividad y, si la respuesta es afirmativa, cuándo hacerlo.

El siguiente flujo de trabajo muestra cómo implementar una estrategia de reintento personalizada. Utiliza la misma actividad unreliableActivity que los flujos de trabajo DecoratorRetryWorkflow y AsyncExecutorRetryWorkflow. La interfaz de flujo de trabajo se implementa en RetryWorkflow y tiene un método, process, que es el punto de entrada del flujo de trabajo. El proceso de trabajo de flujo de trabajo se implementa en CustomLogicRetryWorkflowImpl, de la siguiente manera:

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

El flujo de trabajo funciona de la siguiente manera:

  1. process llama al método asíncrono callActivityWithRetry.

  2. callActivityWithRetry crea un objeto Settable<Throwable> llamado failure que se utiliza para indicar si se ha producido un error en la actividad. Settable<T> proviene de Promise<T> y funciona de forma muy parecida, pero en este caso usted establece manualmente el valor de un objeto Settable<T>.

  3. callActivityWithRetry implementa una clase TryCatchFinally anidada de manera anónima para gestionar cualquier excepción lanzada por unreliableActivity. Para obtener más información sobre cómo tratar excepciones lanzadas por un código asíncrono, consulte Excepciones del AWS Flow Framework para Java.

  4. doTry ejecuta unreliableActivity.

  5. Si unreliableActivity lanza una excepción, el marco de trabajo llama a doCatch y le pasa el objeto de excepción. doCatch establece failure en el objeto de excepción, lo que indica que se ha producido un error en la actividad y pone el objeto en el estado ready.

  6. doFinally comprueba si failure está listo, lo que solo será "true" si doCatch ha establecido failure.

    • Si failure está listo, doFinally no hace nada.

    • Si failure no está listo, la actividad completada y doFinally establecen el error en null.

  7. callActivityWithRetry llama al método asíncrono retryOnFailure y le pasa el error. Dado que el error es un tipo Settable<T>, callActivityWithRetry la ejecución se aplaza hasta que el error esté listo, lo que ocurre después de que se completa TryCatchFinally.

  8. retryOnFailure obtiene el valor del error.

    • Si el error se establece en null, el reintento se ha realizado con éxito. retryOnFailure no hace nada, lo cual termina el proceso de reintento.

    • Si el error se establece en un objeto de excepción y shouldRetry devuelve "true", retryOnFailure llama a callActivityWithRetry para reintentar la actividad.

      shouldRetry implementa la lógica personalizada para decidir si vuelve a intentar una actividad en la que se ha producido un error. Para simplificar, shouldRetry siempre devuelve true y retryOnFailure ejecuta la actividad inmediatamente, pero puede implementar una lógica más sofisticada según considere apropiado.

  9. Los pasos 2 al 8 se repiten hasta que unreliableActivity se completa o bien hasta que shouldRetry decide interrumpir el proceso.

nota

doCatch no gestiona el proceso de reintento, simplemente establece el error para indicar que se ha producido un error en la actividad. El proceso de reintento es gestionado por el método asíncrono retryOnFailure, que aplaza la ejecución hasta que se completa TryCatch. El motivo de este enfoque es que, si reintenta una actividad en doCatch, no es posible cancelarla. Volver a intentar la actividad retryOnFailure le permite ejecutar actividades que se pueden cancelar.