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.
¿Quieres comentar?
Inicia sesión con Telegram para participar en la conversación