Alejandría

Busca en Alejandría

Lenguajes y marcos

Resilience4j

Biblioteca Java ligera de circuit breaker, retry, rate limiter, bulkhead y time limiter.

01

En pocas palabras

Librería de tolerancia a fallos para Java inspirada en Hystrix, diseñada con programación funcional, módulos independientes e integración con Spring Boot y Micrometer.

Qué problema resuelve

Implementar a mano reintentos, timeouts y circuit breakers de forma inconsistente en cada integración.

Cuándo utilizarlo

Servicios JVM que llaman a APIs externas o internas. Ejemplo: proteger la consulta al buró de crédito con timeout, un reintento y circuit breaker.

02

De la idea a la práctica

Funcionamiento, ejemplos y decisiones para pasar al siguiente nivel.

Módulos

Módulo Función
CircuitBreaker Abre el circuito según tasa de fallos o de llamadas lentas
Retry Reintentos con backoff configurable y filtros por excepción
TimeLimiter Límite de tiempo para llamadas asíncronas (CompletableFuture)
Bulkhead Limita llamadas concurrentes (semáforo o pool de hilos)
RateLimiter Limita llamadas por periodo

Se combinan decorando la misma función. Con anotaciones en Spring, el orden de aplicación por defecto es Retry(CircuitBreaker(RateLimiter(TimeLimiter(Bulkhead(función))))).

Ejemplo con Spring Boot

@Service
class ClienteBuro {

    @CircuitBreaker(name = "buro", fallbackMethod = "pendiente")
    @Retry(name = "buro")
    @Bulkhead(name = "buro")
    public ResultadoBuro consultar(String documento) {
        return restClient.get().uri("/historial/{doc}", documento)
            .retrieve().body(ResultadoBuro.class);
    }

    private ResultadoBuro pendiente(String documento, Throwable causa) {
        return ResultadoBuro.pendiente(documento);   // la solicitud queda PENDIENTE_BURO
    }
}
resilience4j:
  circuitbreaker:
    instances:
      buro:
        sliding-window-type: COUNT_BASED
        sliding-window-size: 20
        minimum-number-of-calls: 10
        failure-rate-threshold: 50
        slow-call-duration-threshold: 2s
        slow-call-rate-threshold: 50
        wait-duration-in-open-state: 30s
        permitted-number-of-calls-in-half-open-state: 3
  retry:
    instances:
      buro:
        max-attempts: 2
        wait-duration: 200ms
        enable-exponential-backoff: true
        enable-randomized-wait: true
        retry-exceptions:
          - java.net.SocketTimeoutException
          - org.springframework.web.client.HttpServerErrorException
  bulkhead:
    instances:
      buro:
        max-concurrent-calls: 20

Requiere spring-boot-starter-aop (o el starter de AspectJ según la versión) para que funcionen las anotaciones. El timeout de la llamada síncrona se configura en el propio cliente HTTP; TimeLimiter aplica solo a llamadas asíncronas.

Observabilidad

Con Actuator y Micrometer se exponen métricas como resilience4j_circuitbreaker_state, resilience4j_circuitbreaker_calls y resilience4j_retry_calls, además del endpoint /actuator/circuitbreakers. Alerta cuando un circuito permanezca abierto y grafica la tasa de fallbacks: es una degradación visible para el negocio.

Antipatrones frecuentes

  • Reintentar excepciones de negocio (400, validación): nunca van a tener éxito.
  • max-attempts alto en varias capas, que multiplica la carga sobre la dependencia caída.
  • Fallbacks que ocultan errores sin métricas: el sistema «funciona» mientras pierde ventas.

Uso programático sin anotaciones

CircuitBreaker cb = circuitBreakerRegistry.circuitBreaker("buro");
Retry retry = retryRegistry.retry("buro");
Bulkhead bulkhead = bulkheadRegistry.bulkhead("buro");

Supplier<ResultadoBuro> llamada = Decorators.ofSupplier(() -> cliente.consultar(documento))
    .withBulkhead(bulkhead)
    .withCircuitBreaker(cb)
    .withRetry(retry)
    .withFallback(List.of(CallNotPermittedException.class, BulkheadFullException.class),
                  e -> ResultadoBuro.pendiente(documento))
    .decorate();

ResultadoBuro resultado = llamada.get();

El uso programático hace explícito el orden de los decoradores y funciona fuera de beans de Spring (por ejemplo, en librerías o en código sin AOP).

Timeouts del cliente HTTP

@Bean
RestClient clienteBuro(BuroProperties props) {
    var factory = new SimpleClientHttpRequestFactory();
    factory.setConnectTimeout(Duration.ofSeconds(1));
    factory.setReadTimeout(props.timeout());          // 3 s
    return RestClient.builder()
        .baseUrl(props.baseUrl().toString())
        .requestFactory(factory)
        .build();
}

Sin timeouts en el cliente, CircuitBreaker solo se entera del fallo cuando la llamada termina, que puede ser nunca.

Rate limiter para cuotas de terceros

resilience4j:
  ratelimiter:
    instances:
      buro:
        limit-for-period: 50 # el contrato permite 50 consultas por segundo
        limit-refresh-period: 1s
        timeout-duration: 200ms # espera máxima por un permiso antes de fallar

Protege al proveedor y evita penalizaciones por exceder la cuota contratada. Si hay varias réplicas, el límite es por instancia: divide la cuota entre réplicas o usa un limitador distribuido en el gateway.

Probar el comportamiento

@Test
void abreElCircuitoTrasFallosYUsaFallback() {
    wireMock.stubFor(get(urlPathMatching("/historial/.*")).willReturn(serverError()));
    IntStream.range(0, 20).forEach(i -> servicio.consultar("123"));

    assertThat(circuitBreakerRegistry.circuitBreaker("buro").getState()).isEqualTo(CircuitBreaker.State.OPEN);
    assertThat(servicio.consultar("123").estado()).isEqualTo(Estado.PENDIENTE_BURO);
}

Usa en las pruebas una configuración con ventanas y esperas cortas para que los cambios de estado ocurran en milisegundos.

Alternativas

Spring Cloud Circuit Breaker ofrece una abstracción sobre Resilience4j; en Kubernetes, un service mesh puede aplicar timeouts, reintentos y outlier detection fuera del código; Dapr define políticas declarativas. La lógica de fallback de negocio, en cualquier caso, sigue en la aplicación.

Criterios de implementación

Configura una instancia por dependencia, nunca una global. Deriva los valores de la latencia y la tasa de error observadas, siguiendo los patrones de resiliencia. Prueba el comportamiento con inyección de fallos (WireMock con retardos o errores) en las pruebas de integración de Spring Boot.

Continúa en las fuentes

Documentación oficial para verificar detalles y explorar más.