🛡️ Resilience4j Cheatsheet Completo 🛡️
Resilience4j es una librería de resiliencia ligera y modular para Java. Proporciona patrones de tolerancia a fallos como Circuit Breaker, Rate Limiter, Retry, Bulkhead y Time Limiter para mejorar la fiabilidad y la estabilidad de los microservicios en un entorno distribuido.
1. 🌟 Conceptos Clave de Resiliencia
- Tolerancia a Fallos: La capacidad de un sistema para seguir funcionando correctamente a pesar de los fallos de algunos de sus componentes.
- Patrones de Resiliencia: Estrategias de diseño para construir sistemas tolerantes a fallos.
- Circuit Breaker (Cortocircuito): Un patrón que evita que una aplicación intente continuamente una operación que probablemente fallará, dándole al sistema fallido tiempo para recuperarse.
- Rate Limiter (Limitador de Tasa): Un patrón para limitar el número de solicitudes que una aplicación puede enviar o recibir en un período de tiempo, previniendo la sobrecarga.
- Retry (Reintento): Un patrón para reintentar automáticamente una operación fallida, especialmente útil para fallos transitorios.
- Bulkhead (Mamparo): Un patrón que aísla los recursos del sistema, de modo que un fallo en un componente no derribe todo el sistema.
- Time Limiter (Limitador de Tiempo): Un patrón que establece un límite de tiempo para que una operación se complete, cancelándola si excede el tiempo.
- Decoración de Funciones: Resilience4j funciona decorando funciones Java (lambdas,
Callable,Supplier,Function,Consumer, etc.) para aplicar los patrones.
2. 🛠️ Configuración Inicial (Spring Boot)
- Añadir dependencias en
pom.xml(Maven):- Necesitarás el starter de Spring Boot para Resilience4j y las dependencias de los módulos específicos de resiliencia que quieras usar.
<dependencies> <parent>...</parent> <!-- Tu parent de Spring Boot --> <!-- Starter principal de Resilience4j para Spring Boot --> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-spring-boot3</artifactId> <!-- Para Spring Boot 3+ --> <!-- Para Spring Boot 2.x, usa resilience4j-spring-boot2 --> <version>2.2.0</version> <!-- Usar la versión más reciente --> </dependency> <!-- Módulos individuales (se incluyen por defecto en spring-boot-starter, pero puedes ser explícito) --> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-circuitbreaker</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-ratelimiter</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-retry</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-bulkhead</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-timelimiter</artifactId> <version>2.2.0</version> </dependency> <!-- Para métricas (Actuator y Prometheus/Grafana) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> <scope>runtime</scope> </dependency> </dependencies>
3. 📝 Configuración en application.yml (o .properties)
Resilience4j se configura principalmente en los archivos de propiedades, definiendo instancias para cada patrón.
# src/main/resources/application.yml
resilience4j:
circuitbreaker: # Configuración de Circuit Breaker
instances:
myProductService: # Nombre de la instancia del Circuit Breaker
registerHealthIndicator: true # Registrar en /actuator/health
failureRateThreshold: 50 # Porcentaje de fallos para abrir el circuito (default: 50)
minimumNumberOfCalls: 5 # Mínimo de llamadas en el período deslizante para calcular la tasa de fallos
slidingWindowType: COUNT_BASED # Tipo de ventana deslizante (COUNT_BASED o TIME_BASED)
slidingWindowSize: 10 # Tamaño de la ventana deslizante (10 llamadas o 10 segundos)
waitDurationInOpenState: 5s # Duración de espera en estado OPEN (default: 60s)
permittedNumberOfCallsInHalfOpenState: 3 # Llamadas permitidas en estado HALF_OPEN
automaticTransitionFromOpenToHalfOpenEnabled: true # Transición automática a HALF_OPEN
recordExceptions:
- org.springframework.web.client.ResourceAccessException # Registrar esta excepción como fallo
ignoreExceptions:
- com.example.myapp.exceptions.NotFoundException # Ignorar esta excepción (no la cuenta como fallo)
myPaymentService:
baseConfig: default # Hereda de la configuración 'default'
failureRateThreshold: 70
ratelimiter: # Configuración de Rate Limiter
instances:
myExternalApi: # Nombre de la instancia
limitForPeriod: 10 # Número de llamadas permitidas por período
limitRefreshPeriod: 1s # Duración del período para el límite (default: 500ms)
timeoutDuration: 0s # Tiempo de espera para adquirir un permiso (0s = no esperar)
anotherApi:
limitForPeriod: 5
limitRefreshPeriod: 5s
retry: # Configuración de Retry
instances:
myDbOperation: # Nombre de la instancia
maxAttempts: 3 # Número máximo de reintentos
waitDuration: 1s # Duración de espera entre reintentos
retryExceptions:
- java.io.IOException
- java.sql.SQLException
ignoreExceptions:
- com.example.myapp.exceptions.ValidationException
paymentRetry:
maxAttempts: 5
waitDuration: 500ms
exponentialBackoffMultiplier: 2 # Duplica el tiempo de espera en cada reintento
bulkhead: # Configuración de Bulkhead (Thread Pool Bulkhead)
instances:
myHeavyService: # Nombre de la instancia
maxWaitDuration: 0 # Tiempo de espera para adquirir un permiso (0ms = no esperar)
maxConcurrentCalls: 5 # Número máximo de llamadas concurrentes (Fixed-Size Bulkhead)
threadPoolBulkhead: # Bulkhead con Thread Pool dedicado
instances:
myThreadPoolHeavyService: # Nombre de la instancia del Thread Pool Bulkhead
maxThreadPoolSize: 10 # Tamaño máximo del pool de hilos
coreThreadPoolSize: 5 # Hilos core
queueCapacity: 20 # Capacidad de la cola
timelimiter: # Configuración de Time Limiter
instances:
longRunningTask: # Nombre de la instancia
timeoutDuration: 2s # Duración de timeout para la llamada
cancelRunningFuture: true # Cancelar la tarea si excede el tiempo
# Configuración de Actuator para exponer métricas de Resilience4j
management:
endpoints:
web:
exposure:
include: health,info,prometheus # Exponer health, info y prometheus
endpoint:
health:
show-details: always
metrics:
export:
prometheus:
enabled: true
4. 🚀 Uso de Patrones en Código Java
4.1. Con Anotaciones (Recomendado para Spring Boot)
- Aplica las anotaciones directamente a los métodos de tu servicio.
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
import io.github.resilience4j.retry.annotation.Retry;
import io.github.resilience4j.bulkhead.annotation.Bulkhead;
import io.github.resilience4j.timelimiter.annotation.TimeLimiter;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture; // Para TimeLimiter asíncrono
import java.util.concurrent.CompletionStage; // Para TimeLimiter asíncrono
// Excepción personalizada de ejemplo
class ExternalServiceException extends RuntimeException {
public ExternalServiceException(String msg) { super(msg); }
}
@Service
public class ExternalApiService {
private int callCount = 0;
// Circuit Breaker
@CircuitBreaker(name = "myProductService", fallbackMethod = "getProductsFallback")
public String getProductsFromExternalService() {
callCount++;
System.out.println("Llamada al servicio externo de productos. Intento #" + callCount);
if (callCount % 3 != 0) { // Simula un fallo en 2 de cada 3 llamadas
throw new ExternalServiceException("Error simulado del servicio externo");
}
return "Lista de productos obtenida";
}
private String getProductsFallback(Throwable t) { // Método fallback debe tener la misma firma o aceptar un Throwable
System.err.println("Fallback para getProductsFromExternalService: " + t.getMessage());
return "Productos por defecto / Cache";
}
// Rate Limiter
@RateLimiter(name = "myExternalApi", fallbackMethod = "rateLimitFallback")
public String callExternalApi() {
System.out.println("Llamada permitida por Rate Limiter");
return "Respuesta de API";
}
private String rateLimitFallback(Throwable t) {
System.err.println("Fallback por límite de tasa: " + t.getMessage());
return "Límite de tasa excedido";
}
// Retry
@Retry(name = "myDbOperation", fallbackMethod = "dbOperationFallback")
public String performDbOperation() throws IOException {
System.out.println("Intentando operación de base de datos...");
if (Math.random() > 0.5) { // Simula un fallo transitorio 50% de las veces
throw new IOException("Fallo de conexión a DB simulado");
}
return "Operación DB exitosa";
}
private String dbOperationFallback(IOException e) {
System.err.println("Fallback para operación DB después de reintentos: " + e.getMessage());
return "Datos de DB en caché";
}
// Bulkhead (Thread Pool Bulkhead para operaciones asíncronas)
// Para Thread Pool Bulkhead, el método debe devolver CompletableFuture o CompletionStage
@Bulkhead(name = "myThreadPoolHeavyService", type = Bulkhead.Type.THREADPOOL, fallbackMethod = "heavyServiceFallback")
public CompletionStage<String> callHeavyServiceAsync() {
return CompletableFuture.supplyAsync(() -> {
System.out.println("Ejecutando tarea pesada en pool de hilos...");
try {
Thread.sleep(2000); // Tarea que tarda 2 segundos
} catch (InterruptedException e) { Thread.currentThread().interrupt(); }
return "Resultado de tarea pesada";
});
}
private CompletionStage<String> heavyServiceFallback(Throwable t) {
System.err.println("Fallback por Bulkhead/timeout en tarea pesada: " + t.getMessage());
return CompletableFuture.completedFuture("Resultado por defecto de tarea pesada");
}
// Time Limiter (con Circuit Breaker y Retry)
@CircuitBreaker(name = "longRunningTask", fallbackMethod = "timeLimitAndCircuitBreakerFallback")
@Retry(name = "longRunningTask")
@TimeLimiter(name = "longRunningTask", fallbackMethod = "timeLimitAndCircuitBreakerFallback")
public CompletionStage<String> callLongRunningTask() {
return CompletableFuture.supplyAsync(() -> {
System.out.println("Iniciando tarea de larga duración...");
try {
Thread.sleep(3000); // Simula una tarea que excede el timeout de 2s
} catch (InterruptedException e) { Thread.currentThread().interrupt(); }
return "Tarea de larga duración completada";
});
}
private CompletionStage<String> timeLimitAndCircuitBreakerFallback(Throwable t) {
System.err.println("Fallback para tarea de larga duración: " + t.getMessage());
return CompletableFuture.completedFuture("Resultado de timeout");
}
}
4.2. Con Decoradores Funcionales (Programáticamente)
- Útil para casos donde las anotaciones no son adecuadas (ej. funciones que no son Spring beans).
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryRegistry;
import io.vavr.CheckedFunction0; // Para funciones que lanzan excepciones
public class FunctionalDecoratorsExample {
private final CircuitBreaker circuitBreaker;
private final Retry retry;
public FunctionalDecoratorsExample(CircuitBreakerRegistry cbRegistry, RetryRegistry retryRegistry) {
this.circuitBreaker = cbRegistry.circuitBreaker("myProductService");
this.retry = retryRegistry.retry("myDbOperation");
}
public String callProductService() {
CheckedFunction0<String> decoratedSupplier = CircuitBreaker.decorateCheckedSupplier(
circuitBreaker,
() -> {
// Lógica original del servicio
System.out.println("Calling product service via functional decorator.");
if (Math.random() < 0.5) { throw new ExternalServiceException("Simulated failure."); }
return "Product list (functional)";
});
try {
return decoratedSupplier.apply(); // Ejecuta la función decorada
} catch (Throwable e) {
System.err.println("Functional CB Fallback: " + e.getMessage());
return "Fallback product list (functional)";
}
}
public String callDbOperationWithRetry() {
CheckedFunction0<String> retryableSupplier = Retry.decorateCheckedSupplier(
retry,
() -> {
System.out.println("Calling DB operation via functional decorator.");
if (Math.random() < 0.8) { throw new IOException("Simulated DB transient failure."); }
return "DB operation success (functional)";
});
try {
return retryableSupplier.apply();
} catch (Throwable e) {
System.err.println("Functional Retry Fallback: " + e.getMessage());
return "DB operation fallback (functional)";
}
}
}
5. 📊 Métricas y Monitoreo
- Resilience4j se integra con Micrometer, lo que permite exponer sus métricas a través de Spring Boot Actuator (
/actuator/prometheus). - Métricas Comunes:
- Circuit Breaker:
resilience4j_circuitbreaker_state(Estado: CLOSED, OPEN, HALF_OPEN),resilience4j_circuitbreaker_calls(Llamadas: successful, failed, not_permitted),resilience4j_circuitbreaker_buffered_calls. - Rate Limiter:
resilience4j_ratelimiter_waiting_threads,resilience4j_ratelimiter_available_permissions. - Retry:
resilience4j_retry_calls(successful_with_retry, successful_without_retry, failed_after_retry, failed_without_retry). - Bulkhead:
resilience4j_bulkhead_available_concurrent_calls,resilience4j_bulkhead_max_allowed_concurrent_calls,resilience4j_bulkhead_waiting_threads. - Time Limiter:
resilience4j_timelimiter_calls.
- Circuit Breaker:
- Visualización: Usa Prometheus para recolectar las métricas y Grafana para visualizarlas en dashboards.
6. 💡 Buenas Prácticas y Consejos
- Empieza por el Circuit Breaker: Es el patrón más fundamental para prevenir fallos en cascada.
- Configura
failureRateThresholdywaitDurationInOpenStateCuidadosamente: Estos son los parámetros más importantes del Circuit Breaker para evitar disparos falsos y permitir una recuperación adecuada. - Define Fallback Methods: Siempre proporciona un método fallback para tus operaciones protegidas. Debe ser rápido, no lanzar excepciones y devolver una respuesta por defecto o en caché.
- Granularidad de Instancias: Crea instancias separadas de cada patrón de resiliencia para cada servicio externo o componente crítico con el que interactúas.
- Usa Anotaciones en Spring Boot: Son más concisas y legibles que los decoradores funcionales para la mayoría de los casos.
- Monitorea tus Métricas: Es crucial monitorear los estados de tus Circuit Breakers, la tasa de uso de tus Rate Limiters, y el rendimiento de tus retries para entender cómo se está comportando tu sistema.
- Combinación de Patrones: Los patrones de resiliencia a menudo se combinan:
- Retry + Circuit Breaker: Reintentar primero un par de veces para fallos transitorios, luego el Circuit Breaker entra en acción si los reintentos fallan persistentemente.
- Bulkhead + Circuit Breaker: El Bulkhead aísla los recursos, mientras que el Circuit Breaker protege contra fallos de un servicio.
- Time Limiter + Circuit Breaker/Retry: Establece un tiempo máximo para que las operaciones asíncronas completen.
- Excepciones Específicas: Configura
recordExceptionsyignoreExceptionspara registrar o ignorar tipos de excepciones específicos. - No para Lógica de Negocio: La resiliencia se aplica a las interacciones con sistemas externos (DB, APIs, microservicios), no a la lógica de negocio interna.
- Documenta tus Políticas: Documenta claramente las políticas de resiliencia aplicadas a cada servicio.
Este cheatsheet te proporciona una referencia completa de Resilience4j, cubriendo sus conceptos esenciales de resiliencia, cómo configurarlo en Spring Boot, el uso de sus anotaciones en código Java, las métricas clave y las mejores prácticas para construir microservicios robustos y tolerantes a fallos.