Resilience4j en Java: circuit breaker, retry y rate limiter paso a paso

java Resilience4j en Java: circuit breaker, retry y rate limiter paso a paso

Resilience4j en Java: circuit breaker, retry y rate limiter paso a paso

Este artículo muestra cómo integrar Resilience4j en aplicaciones Java (vanilla y Spring Boot). Verás ejemplos programáticos, configuración, pruebas y puntos de observabilidad. Vamos al grano.

1. ¿Por qué Resilience4j?

Resilience4j es modular, ligero y diseñado para Java 8+. Ofrece CircuitBreaker, Retry, RateLimiter, Bulkhead y TimeLimiter. A diferencia de Hystrix, no depende de Netflix OSS y se integra bien con Micrometer.

2. Dependencias (Maven)

<dependency>
   <groupId>io.github.resilience4j</groupId>
   <artifactId>resilience4j-all</artifactId>
   <version>1.7.1</version>
</dependency>

<!-- Para Spring Boot -->
<dependency>
   <groupId>io.github.resilience4j</groupId>
   <artifactId>resilience4j-spring-boot2</artifactId>
   <version>1.7.1</version>
</dependency>

<dependency>
  <groupId>io.micrometer</groupId>
  <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

3. Uso programático (vanilla Java)

Ejemplo simple que envuelve una llamada remota con CircuitBreaker y Retry programáticamente.

import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryConfig;

import java.time.Duration;
import java.util.function.Supplier;

CircuitBreakerConfig cbConfig = CircuitBreakerConfig.custom()
    .failureRateThreshold(50)
    .waitDurationInOpenState(Duration.ofSeconds(30))
    .slidingWindowSize(20)
    .build();

CircuitBreaker circuitBreaker = CircuitBreaker.of("backendCB", cbConfig);

RetryConfig retryConfig = RetryConfig.custom()
    .maxAttempts(3)
    .waitDuration(Duration.ofMillis(500))
    .build();

Retry retry = Retry.of("backendRetry", retryConfig);

Supplier remoteCall = () -> {
    // llamada HTTP/síncrona que puede lanzar RuntimeException
    if (Math.random() < 0.7) throw new RuntimeException("fail");
    return "OK";
};

Supplier decorated = CircuitBreaker.decorateSupplier(circuitBreaker,
                     Retry.decorateSupplier(retry, remoteCall));

try {
    String result = decorated.get();
    System.out.println(result);
} catch (Exception e) {
    // fallback o lógica compensatoria
    System.out.println("fallback: " + e.getMessage());
}

4. Integración con Spring Boot (anotaciones)

Habilita Resilience4j en Spring Boot y usa anotaciones sobre servicios.

@SpringBootApplication
public class App { public static void main(String[] args){ SpringApplication.run(App.class, args);} }

@Service
public class RemoteService {

    @io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker(name = "backend", fallbackMethod = "fallback")
    @io.github.resilience4j.retry.annotation.Retry(name = "backend")
    public String callExternal() {
        // llamada al cliente HTTP (RestTemplate / WebClient / Feign)
        if (Math.random() < 0.7) throw new RuntimeException("remote error");
        return "ok";
    }

    public String fallback(Exception ex) {
        // firma compatible: mismo return type, último parámetro la excepción opcional
        return "fallback: " + ex.getMessage();
    }
}

5. Configuración típica application.yml

resilience4j:
  circuitbreaker:
    instances:
      backend:
        registerHealthIndicator: true
        slidingWindowSize: 20
        minimumNumberOfCalls: 5
        permittedNumberOfCallsInHalfOpenState: 3
        waitDurationInOpenState: 30s
        failureRateThreshold: 50
  retry:
    instances:
      backend:
        maxAttempts: 3
        waitDuration: 500ms
  ratelimiter:
    instances:
      backend:
        limitForPeriod: 10
        limitRefreshPeriod: 1s

6. Pruebas unitarias e integración

Usa WireMock o MockWebServer para simular fallos y medir comportamiento del CB.

@ExtendWith(SpringExtension.class)
@SpringBootTest
public class RemoteServiceTest {

    @Autowired RemoteService remoteService;
    @RegisterExtension
    static WireMockExtension wm = WireMockExtension.newInstance().options(wireMockConfig().dynamicPort()).build();

    @BeforeEach
    void setup() {
        // simulamos 5 respuestas 500
        wm.stubFor(get(urlEqualTo("/api"))
            .willReturn(aResponse().withStatus(500)));
    }

    @Test
    void shouldOpenCircuitAfterFailures() {
        for (int i=0;i<6;i++) {
            try { remoteService.callExternal(); } catch (Exception ignored) {}
        }
        // leer estado del CircuitBreaker desde el registry
        CircuitBreaker cb = CircuitBreakerRegistry.ofDefaults().circuitBreaker("backend");
        assertEquals(CircuitBreaker.State.OPEN, cb.getState());
    }
}

7. Observabilidad y métricas

Con Micrometer exporta métricas a Prometheus. Resilience4j expone métricas por CB/Retry/Bulkhead:

  • resilience4j_circuitbreaker_state
  • resilience4j_circuitbreaker_calls
  • resilience4j_retry_calls

Con Grafana puedes crear alertas sobre tasa de fallos o tiempo en estado OPEN.

8. Buenas prácticas y errores comunes

  • Define correctamente minimumNumberOfCalls: si es muy bajo, abrirá por ruido; si es muy alto, tardará en reaccionar.
  • No combines retries con idempotencia débil sobre operaciones mutables sin protección (puede causar efectos duplicados).
  • Usa Bulkhead para aislar recursos y evitar que un subsistema agote threads comunes.
  • Evita retry con backoff fijo en picos; prefiere exponencial con jitter.

Siguiente paso: añade TimeLimiter para llamadas que tardan demasiado y combina con CompletableFuture o WebClient para no bloquear hilos.

Consejo avanzado: instrumenta el CircuitBreakerRegistry con un listener que emita trazas específicas cuando un CB cambia a OPEN—así puedes correlacionar la telemetría con traces en Jaeger/Zipkin y automatizar mitigaciones (ej. degradar características).

Advertencia de seguridad: no expongas métricas detalladas o trazas con datos sensibles en endpoints públicos; filtra y controla el acceso a /actuator/prometheus y a endpoints de tracing.

Comentarios
¿Quieres comentar?

Inicia sesión con Telegram para participar en la conversación


Comentarios (0)

Aún no hay comentarios. ¡Sé el primero en comentar!

Iniciar Sesión