Configuración y Uso de Spring Retry

Spring Retry es un módulo del framework Spring que facilita la reejecución de operaciones que han fallado temporalmente.

Casos de Uso Comunes

  • Interacciones de Red y Llamadas Remotas: Manejo de fallos transitorios en comunicaicones de red o llamadas a servicios externos debido a inestabilidad o indisponibilidad temporal.
  • Operaciones con Bases de Datos: Recuperación ante problemas momentáneos al interactuar con bases de datos, como bloqueos o desconexiones.
  • Dependencias Externas: Gestión de fallos en servicios, dispositivos o sistemas externos que pueden presentar fallos ocasionales, asegurando la resiliencia de la aplicación.
  • Control de Concurrencia: Mitigación de errores debidos a condiciones de carrera o problemas de concurrencia en entornos multihilo.
  • Lógica de Negocio Compleja: Cuando ciertas partes de la lógica de negocio pueden requerir múltiples intentos para completarse con éxito.

Generalmente, las operaciones que se benefician de reintentos son aquellas que experimentan fallos "instantáneos" o transitorios, donde un nuevo intento tiene una alta probabilidad de éxito.

Implementación

1. Añadir la Dependencia (Maven)


<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>1.3.4</version&gt>
</dependency>

2. Habilitar Spring Retry

Añada la anotación @EnableRetry a su clase de configuración principal.


@Configuration
@EnableRetry
public class AppConfig {
    // ... otros beans y configuración
}

3. Utilizar la Anotación @Retryable

Marque los métodos que desea que sean reintentables con @Retryable.


@Retryable
public void metodoConReintentos() {
    System.out.println("Ejecutando método con reintentos en: " + System.currentTimeMillis());
    throw new RuntimeException("Error simulado para reintento.");
}

Por defecto, este método se reintentará hasta 3 veces con un intervalo de 1 segundo entre intentos.

Detalles de la Anotación @Retryable (v1.3.4)

  • maxAttempts: Número máximo de intentos (incluyendo el primer intento fallido). Por defecto: 3.
  • maxAttemptsExpression: Expresión SpEL para calcular el número máximo de intentos.
  • value / include: Clases de excepciones que deben ser reintentables.
  • exclude: Clases de excepciones que NO deben ser reintentables.
  • backoff: Configura la política de espera (backoff) entre reintentos.
  • recover: Nombre del método en una clase recuperadora anotado con @Recover que se llamará si todos los reintentos fallan.
  • interceptor: Nombre de un bean interceptor personalizado. Si se usa, los demás atributos se ignoran.
  • label: Clave utilizada para el estado en reintentos con estado (stateful).
  • stateful: Indica si el reintento debe mantener estado entre llamadas sucesivas con los mismos parámetros. Si es false, las excepciones reintentables no se vuelven a lanzar.
  • exceptionExpression: Expresión SpEL para determinar condicionalmente si se debe reintentar después de que SimpleRetryPolicy.canRetry() retorne true. Evalúa el último Throwable.

Detalles de la Anotación @Backoff

  • value: Tiempo de espera en milisegundos (usado si delay no está especificado). Por defecto: 1000.
  • delay: Tiempo de espera inicial en milisegundos. Usado como base para el cálculo en políticas exponenciales y como mínimo en políticas uniformes.
  • maxDelay: Tiempo máximo de espera en milisegundos entre reintentos.
  • multiplier: Factor de multiplicación para calcular el siguiente tiempo de espera en políticas exponenciales. Debe ser positivo. Por defecto: 0 (ignorado).
  • delayExpression: Expresión SpEL para calcular el tiempo de espera inicial.
  • maxDelayExpression: Expresión SpEL para calcular el tiempo máximo de espera.
  • multiplierExpression: Expresión SpEL para calcular el multiplicador.
  • random: Si es true y multiplier es positivo, el tiempo de espera se aleatoriza dentro de un rango (multiplicador).
  • randomExpression: Expresión SpEL para calcular el valor de random.

Las expresiones SpEL pueden referenciar parámetros del método, valores de retorno, y otros beans contextuales (usando #root para el objeto principal evaluado).

4. Anotación @Recover

Defina un método recuperador para manejar la situación cuando todos los reintentos fallan. Este método debe tener la misma firma que el método reintentable, pero aceptando un Throwable como parámetro.


@Recover
public void fallback(Throwable cause) {
    System.err.println("Todos los reintentos fallaron. Causa: " + cause.getMessage());
    // Lógica para manejar el fallo final, como registrar el error o notificar.
}

5. Uso de RetryTemplate

Para un control más granular, puede configurar y usar RetryTemplate directamente.


@Bean
public RetryTemplate retryTemplate() {
    RetryTemplate template = new RetryTemplate();

    // Configuración de la política de espera (BackOffPolicy)
    ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
    backOffPolicy.setInitialInterval(500L); // Intervalo inicial de 500ms
    backOffPolicy.setMultiplier(2.0);      // Duplicar el intervalo en cada reintento
    backOffPolicy.setMaxInterval(10000L);   // Intervalo máximo de 10 segundos
    template.setBackOffPolicy(backOffPolicy);

    // Configuración de la política de reintentos (RetryPolicy)
    SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
    retryPolicy.setMaxAttempts(5); // Intentar hasta 5 veces
    template.setRetryPolicy(retryPolicy);

    // Opcionalmente, configurar listeners
    // template.setListeners(new RetryListener[]{...});

    return template;
}

Uso del template:


@Autowired
private RetryTemplate retryTemplate;

public void ejecutarConTemplate() {
    try {
        String resultado = retryTemplate.execute(context -> {
            System.out.println("Intentando operación dentro de RetryTemplate. Intento: " + context.getRetryCount());
            // Lógica de negocio que puede fallar
            return realizarOperacionCritica();
        });
        System.out.println("Operación completada exitosamente: " + resultado);
    } catch (Throwable e) {
        System.err.println("La operación falló después de todos los reintentos.");
        // Manejo final del fallo
    }
}

private String realizarOperacionCritica() {
    // Simular una operación que puede fallar
    if (Math.random() < 0.8) { // 80% de probabilidad de fallo
        throw new RuntimeException("Fallo simulado en la operación crítica.");
    }
    return "Éxito en la operación crítica";
}

Funcionamiento Interno

Spring Retry opera a través de varios componentes clave:

  • RetryTemplate: El orquestador central. Ejecuta el código proporcionado a través de un RetryCallback y aplica las políticas de reintento y espera configuradas.
  • RetryPolicy: Define las condiciones bajo las cuales se deben realizar los reintentos (p. ej., número máximo de intentos, tipos de excepciones). Implementaciones comunes incluyen SimpleRetryPolicy y CircuitBreakerRetryPolicy.
  • BackOffPolicy: Controla el tiempo de espera entre intentos de reintento. Implementaciones como FixedBackOffPolicy y ExponentialBackOffPolicy ofrecen diferentes estrategias de espera.
  • RetryListener: Permite interceptar y reaccionar a eventos durante el proceso de reintento (antes de cada intento, después de un fallo, etc.).
  • RetryContext: Objeto que encapsula el estado actual del proceso de reintento, como el número de intentos realizados y la última excepción ocurrida. Es utilizado por las políticas para tomar decisiones.
  • BackOffContext: Similar a RetryContext pero específico para la gestión del estado de la política de espera.
  • RetryCallback: Interfaz funcional que contiene la lógica de negocio que se ejecutará y que podría necesitar ser reintentada.
  • RecoveryCallback: Interfaz funcional que contiene la lógica a ejecutar si todos los intentos de reintento fallan.

El flujo general implica que RetryTemplate invoca repetidamente el RetryCallback. Después de cada fallo, la RetryPolicy decide si se debe continuar, y si es así, la BackOffPolicy determina cuánto tiempo esperar antes del siguiente intento.

Etiquetas: Spring retry java Exception Handling resilience

Publicado el 7-26 08:37