¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
¿Buscas nuestro logo?
Aquí te dejamos una copia, pero si necesitas más opciones o quieres conocer más, visita nuestra área de marca.
dev
Raúl Martínez Hace 1 día Cargando comentarios…
En los sistemas distribuidos, las aplicaciones dependen de múltiples servicios externos (APIs, bases de datos, etc.). Un fallo en uno de estos servicios puede causar un fallo en cascada, donde la lentitud o caída de un componente bloquea recursos y tumba toda la aplicación.
El objetivo de Resilience4j es dotar a la aplicación de la capacidad de resistir, adaptarse y recuperarse de estos fallos sin colapsar, mejorando la experiencia del usuario y la estabilidad del sistema.
Resilience4j ha evolucionado significativamente desde su creación en 2015. La biblioteca pasó de ser un proyecto experimental a convertirse en el estándar de facto para tolerancia a fallos en aplicaciones Spring Boot. El hito más importante fue la versión 2.0.0 (2022), que estableció Java 17 como requisito mínimo y crear módulos separados para Spring Boot 2 (resilience4j-spring-boot2) y Spring Boot 3 (resilience4j-spring-boot3).
Actualmente, en su versión 2.3.0 (2025), Resilience4j ofrece integración nativa con Spring Boot 3.2+, soporte completo para programación reactiva, observabilidad mejorada con Actuator y Micrometer y se posiciona como una solución madura y estable para implementar patrones de resiliencia (Circuit Breaker, Retry, Rate Limiter, Bulkhead, Time Limiter) en arquitecturas de microservicios modernas.
Resilience4j implementa varios patrones de resiliencia. Hay que tener en cuenta que esta librería y sus estrategias se pueden aplicar a sistemas que tengan dependencias externas independientemente de su diseño (monolíticos, micros etc..).
Pero, dada la idiosincrasia de los microservicios, su uso y estrategias encajan perfectamente. Los más importantes son:
Estados:
Configuración clave:
Properties:
# Circuit Breaker - Configuración por defecto
resilience4j.circuitbreaker.configs.default.slidingWindowSize=100
resilience4j.circuitbreaker.configs.default.minimumNumberOfCalls=10
resilience4j.circuitbreaker.configs.default.failureRateThreshold=50
resilience4j.circuitbreaker.configs.default.waitDurationInOpenState=60000
resilience4j.circuitbreaker.configs.default.permittedNumberOfCallsInHalfOpenState=10
resilience4j.circuitbreaker.configs.default.automaticTransitionFromOpenToHalfOpenEnabled=true
resilience4j.circuitbreaker.configs.default.registerHealthIndicator=true
# Circuit Breaker - Servicio de Emails (SendGrid)
resilience4j.circuitbreaker.instances.emailService.baseConfig=default
resilience4j.circuitbreaker.instances.emailService.failureRateThreshold=60
resilience4j.circuitbreaker.instances.emailService.waitDurationInOpenState=120000
Busca reintentar una operación que ha fallado por un problema temporal (ej. un timeout de red).


Configuración:
# Retry - Configuración por defecto
resilience4j.retry.configs.default.maxAttempts=3
resilience4j.retry.configs.default.waitDuration=1000
resilience4j.retry.configs.default.exponentialBackoffMultiplier=2
resilience4j.retry.configs.default.retryExceptions=java.net.SocketTimeoutException,java.net.ConnectException
# Retry - Servicio de Emails
resilience4j.retry.instances.emailService.baseConfig=default
resilience4j.retry.instances.emailService.maxAttempts=2
resilience4j.retry.instances.emailService.waitDuration=2000
Nunca uses reintentos automáticos en operaciones que no sean idempotentes, como procesar un pago, para evitar duplicados.
Protege tu API de un número excesivo de peticiones, ya sea por abuso o sobrecarga (ej. Black Friday). Limita el número de peticiones en un periodo de tiempo.
Configuración clave:
resilience4j.ratelimiter:
instances:
publicApi:
limitForPeriod: 100 # 100 peticiones
limitRefreshPeriod: 1s # por segundo
timeoutDuration: 0 # Rechazar inmediatamente si se excede
Aísla los recursos (hilos) para que un servicio lento no acapare todos los recursos de la aplicación. Limita el número de llamadas concurrentes a un servicio específico. Si un servicio se satura, solo afectará a su "compartimento", no al resto de la aplicación.
Configuración clave:
resilience4j.bulkhead:
instances:
emailService:
maxConcurrentCalls: 30 # Máximo 30 llamadas simultáneas
Establece un timeout máximo para una operación. Si la operación tarda más de lo definido, se cancela y lanza una TimeoutException. Es fundamental para evitar hilos bloqueados indefinidamente.
Configuración clave:
# Time Limiter - Configuración por defecto
resilience4j.timelimiter.configs.default.timeoutDuration=3000
# Time Limiter - Servicio de Emails
resilience4j.timelimiter.instances.emailService.timeoutDuration=15000
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
El verdadero poder de Resilience4j reside en combinar patrones. Este es un ejemplo para proteger una llamada a un servicio de pagos.
Lo primero es establecer la configuración por defecto para que quede claro que está activa. Esto aplica a todos los patrones que vamos a explicar a continuación:
resilience4j.auto-configuration.enabled=true
# Circuit Breaker - Servicio de Pagos
resilience4j.circuitbreaker.instances.paymentService.baseConfig=default
resilience4j.circuitbreaker.instances.paymentService.failureRateThreshold=40
resilience4j.circuitbreaker.instances.paymentService.waitDurationInOpenState=180000
# Time Limiter - Servicio de Pagos (10 segundos para dar margen)
resilience4j.timelimiter.instances.paymentService.timeoutDuration=10000
# IMPORTANTE: NO configurar Retry para pagos
# Los pagos NO deben reintentar automáticamente para evitar cobros duplicados
Código Java:
@Service
public class RedsysPaymentServiceImpl {
private static final Logger logger = LoggerFactory.getLogger(RedsysPaymentServiceImpl.class);
private final PaymentRepository paymentRepository;
private final OrderRepository orderRepository;
private final OrderMailPaymentService orderMailPaymentService;
/**
* Procesa el pago Redsys con protección completa de Resilience4j.
*
* Resilience4j:
* - Circuit Breaker: Protege contra fallos del servicio de pagos
* - Retry: NO se usa aquí (pagos no deben reintentar automáticamente)
* - Time Limiter: Cancela si tarda más de 10 segundos
* - Fallback: Marca pedido como pendiente de revisión manual
*/
@CircuitBreaker(name = "paymentService", fallbackMethod = "fallbackProcessRedsysPayment")
@TimeLimiter(name = "paymentService")
public CompletableFuture<RedsysPaymentResponseDTO> processRedsysPayment(RedsysPaymentRequestDTO request) {
return CompletableFuture.supplyAsync(() -> {
logger.info("[RedsysService] Procesando pago para orderId: {}", request.getOrderId());
// Simulación de retardo para Redsys (3 segundos)
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Pago interrumpido", e);
}
// Crear y guardar el pago
Payment payment = Payment.builder()
.orderId(request.getOrderId())
.amount(request.getAmount())
.method(PaymentMethod.REDSYS)
.status(PaymentStatus.COMPLETED)
.transactionId("REDSYS-" + System.currentTimeMillis())
.build();
Payment saved = paymentRepository.save(payment);
// Actualizar estado del pedido a COMPLETED
orderRepository.findByOrderId(request.getOrderId()).ifPresent(order -> {
order.setStatus("COMPLETED");
orderRepository.save(order);
logger.info("[RedsysService] Order actualizado a COMPLETED: {}", order.getOrderId());
// Enviar email de confirmación (protegido con su propio Circuit Breaker)
orderMailPaymentService.sendOrderConfirmationEmail(order.getOrderId());
});
return paymentMapper.toRedsysPaymentResponseDTO(saved);
});
}
/**
* Fallback cuando el servicio de pagos falla.
* IMPORTANTE: NO reintentar pagos automáticamente para evitar cobros duplicados.
*/
private CompletableFuture<RedsysPaymentResponseDTO> fallbackProcessRedsysPayment(
RedsysPaymentRequestDTO request, Exception e) {
logger.error("⚠️ FALLBACK: Servicio de pago Redsys no disponible para orderId={}. Causa: {}",
request.getOrderId(), e.getMessage(), e);
// Marcar el pedido como pendiente de revisión manual
orderRepository.findByOrderId(request.getOrderId()).ifPresent(order -> {
order.setStatus("PENDING_PAYMENT");
orderRepository.save(order);
logger.warn("[RedsysService] Order marcado como PENDING_PAYMENT para revisión manual: {}",
order.getOrderId());
});
// Crear registro de pago fallido para auditoría
Payment failedPayment = Payment.builder()
.orderId(request.getOrderId())
.amount(request.getAmount())
.method(PaymentMethod.REDSYS)
.status(PaymentStatus.FAILED)
.providerResponse("Servicio temporalmente no disponible: " + e.getMessage())
.build();
paymentRepository.save(failedPayment);
// Devolver respuesta indicando que el pago está pendiente
RedsysPaymentResponseDTO fallbackResponse = new RedsysPaymentResponseDTO();
fallbackResponse.setStatus(PaymentStatus.PENDING);
fallbackResponse.setMessage("El servicio de pago no está disponible. " +
"Tu pedido ha sido guardado y será procesado manualmente. " +
"Recibirás un email de confirmación en breve.");
return CompletableFuture.completedFuture(fallbackResponse);
}
}
Los beneficios son, principalmente, dos:
| Si tu servicio... | Patrón recomendado | Ejemplo de uso |
|---|---|---|
| Puede caerse por completo o ser muy inestable | Circuit Breaker + Fallback (obligatorio) | API de pagos, servicio de emails, base de datos externa. |
| Sufre fallos temporales (ej. red) | Retry (solo para operaciones idempotentes) | Lecturas de una base de datos, llamada a una API de consulta. |
| Necesita protección contra sobrecarga o abuso | Rate Limiter | Endpoints públicos de tu API, endpoints de login. |
| Consume muchos recursos y puede afectar a otros | Bulkhead | Operaciones pesadas, queries complejas a la BD. |
| A veces tarda demasiado en responder | Time Limiter | Llamadas a APIs de terceros sin un SLA claro. |
Integrar Resilience4j no es una opción, sino una necesidad en arquitecturas de microservicios. Te permite pasar de un sistema frágil que falla por completo a uno robusto y resiliente que puede soportar fallos de sus dependencias de manera elegante.
Recuerda: "no es cuestión de SI un servicio externo fallará, sino de CUÁNDO. Prepárate."
Con patrones como Circuit Breaker, Retry y Fallback puedes construir aplicaciones que no solo sobreviven a los fallos, sino que se recuperan automáticamente, garantizando una mejor experiencia para el usuario y una mayor tranquilidad para el equipo de desarrollo
Adicionalmente, he generado en mi proyecto infinia-sports unos test de carga con Resilience4j con gatling y he generado unos informes con las tasas de mejora del sistema que son muy significativos.
Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.
Cuéntanos qué te parece.