| = MicroProfile Fault Tolerance - Retry Policy |
| :index-group: MicroProfile |
| :jbake-type: page |
| :jbake-status: published |
| |
| Este es un ejemplo de cómo usar Microprofile @Retry en TomEE. |
| |
| == Retry Feature |
| |
| Microprofile Fault Tolerance tiene una función llamada Reintentar que se puede utilizar |
| para recuperase de una operación del error, invocando la misma operación de nuevo |
| hasta que se alcancen los criterios de detención. |
| |
| La directiva de reintento permite configurar: |
| |
| * *maxRetries*: los reintentos máximos |
| * *delay*: retrasos entre cada reintento |
| * *delayUnit*: la unidad de retardo |
| * *maxDuration*: duración máxima para realizar el reintento. |
| * *durationUnit*: unidad de duración |
| * *jitter:* la variación aleatoria de retrasos de reintento |
| * *jitterDelayUnit:* la unidad de fluctuación |
| * *retryOn:* especifica reintentar en caso de errores |
| * *abortOn:* especifica anular en caso de errores |
| |
| Para utilizar esta función puede anotar una clase y/o método con la anotación `@Retry`. Compruebe la |
| http://download.eclipse.org/microprofile/microprofile-fault-tolerance-1.1/microprofile-fault-tolerance-spec.html[especificación] |
| para más detalles. |
| |
| == Ejemplos |
| |
| === Ejecutar la aplicación |
| |
| [source,java] |
| ---- |
| mvn clean install tomee:run |
| ---- |
| |
| === Ejemplo 1 |
| |
| El método statusOfDay producirá un error tres veces, cada vez, |
| lanzando una 'WeatherGatewayTimeoutException' y como la anotación @Retry |
| está configurada para `retryOn` en caso de error, la libreria FailSafe tomará |
| el valor `maxRetry` y volverá a intentar la misma operación hasta que alcance |
| el número máximo de intentos, que es 3 (valor predeterminado). |
| |
| [source,java] |
| ---- |
| @RequestScoped |
| public class WeatherGateway{ |
| ... |
| @Retry(maxRetry=3, retryOn = WeatherGatewayTimeoutException.class) |
| public String statusOfDay(){ |
| if(counterStatusOfDay.addAndGet(1) <= DEFAULT_MAX_RETRY){ |
| LOGGER.warning(String.format(FORECAST_TIMEOUT_MESSAGE, DEFAULT_MAX_RETRY, counterStatusOfDay.get())); |
| throw new WeatherGatewayTimeoutException(); |
| } |
| return "Today is a sunny day!"; |
| } |
| ... |
| } |
| ---- |
| |
| Llamada de estado del día |
| |
| [source,java] |
| ---- |
| GET http://localhost:8080/mp-faulttolerance-retry/weather/day/status |
| ---- |
| |
| Registro del servidor |
| |
| [source,java] |
| ---- |
| WARNING - Timeout when accessing AccuWeather Forecast Service. Max of Attempts: (3), Attempts: (1) |
| WARNING - Timeout when accessing AccuWeather Forecast Service. Max of Attempts: (3), Attempts: (2) |
| WARNING - Timeout when accessing AccuWeather Forecast Service. Max of Attempts: (3), Attempts: (3) |
| ---- |
| |
| Respuesta |
| |
| [source,java] |
| ---- |
| Today is a sunny day! (¡Hoy es un día soleado!) |
| ---- |
| |
| === Ejemplo 2 |
| |
| El método weekStatus fallará dos veces, cada vez, lanzando una excepción |
| `WeatherGatewayTimeoutException` porque `retryOn` está configurado y |
| en lugar de devolver una respuesta al autor de la llamada, la lógica indica que en |
| el tercer intento, una excepción `WeatherGatewayBusyServiceException` será |
| lanzada. Como la anotación `@Retry` está configurada para `abortOn` en caso que |
| `WeatherGatewayTimeoutException` ocurra, el intento restante no será |
| ejecutado y el autor de la llamada tendra que controlar la excepción. |
| |
| [source,java] |
| ---- |
| @Retry(maxRetries = 3, retryOn = WeatherGatewayTimeoutException.class, abortOn = WeatherGatewayBusyServiceException.class) |
| public String statusOfWeek(){ |
| if(counterStatusOfWeek.addAndGet(1) <= DEFAULT_MAX_RETRY){ |
| LOGGER.warning(String.format(FORECAST_TIMEOUT_MESSAGE_ATTEMPTS, DEFAULT_MAX_RETRY, counterStatusOfWeek.get())); |
| throw new WeatherGatewayTimeoutException(); |
| } |
| LOGGER.log(Level.SEVERE, String.format(FORECAST_BUSY_MESSAGE, counterStatusOfWeek.get())); |
| throw new WeatherGatewayBusyServiceException(); |
| } |
| ---- |
| |
| Llamada de estado de la semana |
| |
| [source,java] |
| ---- |
| GET http://localhost:8080/mp-faulttolerance-retry/weather/week/status |
| ---- |
| |
| Registro del servidor |
| |
| [source,java] |
| ---- |
| WARNING - Timeout when accessing AccuWeather Forecast Service. Max of Attempts: (3), Attempts: (1) |
| WARNING - Timeout when accessing AccuWeather Forecast Service. Max of Attempts: (3), Attempts: (2) |
| WARNING - Timeout when accessing AccuWeather Forecast Service. Max of Attempts: (3), Attempts: (3) |
| SEVERE - Error AccuWeather Forecast Service is busy. Number of Attempts: (4) |
| ---- |
| |
| Respuesta |
| |
| [source,java] |
| ---- |
| WeatherGateway Service is Busy. Retry later |
| ---- |
| |
| === Ejemplo 3 |
| |
| La anotación `@Retry` permite configurar un retraso para ejecutar cada nuevo intento |
| dando la oportunidad al servicio solicitado para recuperarse y contestador de la |
| solicitud correctamente. Para cada nuevo reintento siga el retraso |
| configurar, es necesario establecer `jitter` a cero (0). De lo contrario, el retraso de |
| cada nuevo intento será aleatorio. |
| |
| Analizando los mensajes registrados, es posible ver que todos los intentos |
| toman más o menos el mismo tiempo para ejecutar. |
| |
| [source,java] |
| ---- |
| @Retry(retryOn = WeatherGatewayTimeoutException.class, maxRetries = 5, delay = 500, jitter = 0) |
| public String statusOfWeekend() { |
| if (counterStatusOfWeekend.addAndGet(1) <= 5) { |
| logTimeoutMessage(statusOfWeekendInstant); |
| statusOfWeekendInstant = Instant.now(); |
| throw new WeatherGatewayTimeoutException(); |
| } |
| return "The Forecast for the Weekend is Scattered Showers."; |
| } |
| ---- |
| |
| Llamada de estado de la semana |
| |
| [source,java] |
| ---- |
| GET http://localhost:8080/mp-faulttolerance-retry/weather/weekend/status |
| ---- |
| |
| Registro del servidor |
| |
| [source,java] |
| ---- |
| WARNING - Timeout when accessing AccuWeather Forecast Service. |
| WARNING - Timeout when accessing AccuWeather Forecast Service. Delay before this attempt: (501) millis |
| WARNING - Timeout when accessing AccuWeather Forecast Service. Delay before this attempt: (501) millis |
| WARNING - Timeout when accessing AccuWeather Forecast Service. Delay before this attempt: (501) millis |
| WARNING - Timeout when accessing AccuWeather Forecast Service. Delay before this attempt: (500) millis |
| ---- |
| |
| === Ejemplo 4 |
| |
| Básicamente con el mismo comportamiento del `Ejemplo 3`, este ejemplo establece |
| el `delay` y el `jitter` con 500 milisegundos para crear aleatoriamente un nuevo retardo |
| para cada nuevo intento después del primer error. |
| https://github.com/jhalterman/failsafe/blob/master/src/main/java/net/jodah/failsafe/AbstractExecution.java[AbstractExecution-randomDelay(delay,jitter,random)] puede dar una idea de cómo se calcula el nuevo retraso. |
| |
| Mediante el análisis de los mensajes registrados, es posible ver cuanto |
| cada intento tiene que esperar antes de ejecutarse. |
| |
| [source,java] |
| ---- |
| @Retry(retryOn = WeatherGatewayTimeoutException.class, delay = 500, jitter = 500) |
| public String statusOfMonth() { |
| if (counterStatusOfWeekend.addAndGet(1) <= DEFAULT_MAX_RETRY) { |
| logTimeoutMessage(statusOfMonthInstant); |
| statusOfMonthInstant = Instant.now(); |
| throw new WeatherGatewayTimeoutException(); |
| } |
| return "The Forecast for the Weekend is Scattered Showers."; |
| } |
| ---- |
| |
| Llamada de estado del mes |
| |
| [source,java] |
| ---- |
| GET http://localhost:8080/mp-faulttolerance-retry/weather/month/status |
| ---- |
| |
| Registro del servidor |
| |
| [source,java] |
| ---- |
| WARNING - Timeout when accessing AccuWeather Forecast Service. |
| WARNING - Timeout when accessing AccuWeather Forecast Service. Delay before this attempt: (417) millis |
| WARNING - Timeout when accessing AccuWeather Forecast Service. Delay before this attempt: (90) millis |
| ---- |
| |
| === Ejemplo 5 |
| |
| Si no se establece una condición para que una operación que se vuelva a ejecutar |
| como en los ejemplos anteriores mediante el parámetro `retryOn`, la operación |
| se ejecuta de nuevo para _cualquier_ excepción que se produce. |
| |
| [source,java] |
| ---- |
| @Retry(maxDuration = 1000) |
| public String statusOfYear(){ |
| if (counterStatusOfWeekend.addAndGet(1) <= 5) { |
| logTimeoutMessage(statusOfYearInstant); |
| statusOfYearInstant = Instant.now(); |
| throw new RuntimeException(); |
| } |
| return "WeatherGateway Service Error"; |
| } |
| ---- |
| |
| Llamada de estado del año |
| |
| [source,java] |
| ---- |
| GET http://localhost:8080/mp-faulttolerance-retry/weather/year/statusk |
| ---- |
| |
| Registro del servidor |
| |
| [source,java] |
| ---- |
| WARNING - Timeout when accessing AccuWeather Forecast Service. |
| WARNING - Timeout when accessing AccuWeather Forecast Service. Delay before this attempt: (666) millis |
| WARNING - Timeout when accessing AccuWeather Forecast Service. Delay before this attempt: (266) millis |
| WARNING - Timeout when accessing AccuWeather Forecast Service. Delay before this attempt: (66) millis |
| ---- |
| |
| === Ejecutar las pruebas |
| |
| También puede probarlo utilizando el enlace:src/test/java/org/superbiz/rest/WeatherServiceTest.java[WeatherServiceTest.java] disponible en el proyecto. |
| |
| [source,java] |
| ---- |
| mvn clean test |
| ---- |
| |
| [source,java] |
| ---- |
| [INFO] Results: |
| [INFO] |
| [INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0 |
| ---- |