Circuit Breaker
Circuit Breaker avec Resilience4J
10-15 min
Circuit Breaker avec Resilience4J
Le pattern Circuit Breaker est essentiel pour gérer la résilience dans les architectures microservices. Resilience4J est une bibliothèque légère et fonctionnelle qui implémente ce pattern, ainsi que d’autres patterns de résilience.
1. Configuration de Base
1.1 Ajout des Dépendances
<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>
1.2 Configuration du Circuit Breaker
# application.yml
resilience4j:
circuitbreaker:
instances:
productService:
slidingWindowSize: 10
failureRateThreshold: 50
waitDurationInOpenState: 5000
permittedNumberOfCallsInHalfOpenState: 3
eventConsumerBufferSize: 10
ratelimiter:
instances:
productService:
limitForPeriod: 100
limitRefreshPeriod: 1s
timeoutDuration: 0
retry:
instances:
productService:
maxAttempts: 3
waitDuration: 100
enableExponentialBackoff: true
exponentialBackoffMultiplier: 2
exponentialBackoffMaxDuration: 600
retryExceptions:
- org.springframework.web.client.RestClientException
2. Implémentation du Circuit Breaker
2.1 Service avec Circuit Breaker
@Service
@Slf4j
public class ProductService {
private final CircuitBreaker circuitBreaker;
private final RateLimiter rateLimiter;
private final Retry retry;
private final ProductRepository productRepository;
public ProductService(
CircuitBreakerRegistry circuitBreakerRegistry,
RateLimiterRegistry rateLimiterRegistry,
RetryRegistry retryRegistry,
ProductRepository productRepository) {
this.circuitBreaker = circuitBreakerRegistry.circuitBreaker("productService");
this.rateLimiter = rateLimiterRegistry.rateLimiter("productService");
this.retry = retryRegistry.retry("productService");
this.productRepository = productRepository;
}
public Product getProduct(Long id) {
return circuitBreaker.run(
() -> retry.executeSupplier(
() -> rateLimiter.executeSupplier(
() -> productRepository.findById(id)
.orElseThrow(() -> new ProductNotFoundException(id))
)
),
throwable -> {
log.error("Error fetching product: {}", throwable.getMessage());
return new Product(id, "Fallback Product", "Service unavailable",
BigDecimal.ZERO, 0);
}
);
}
}
2.2 Configuration des Événements
@Configuration
public class CircuitBreakerConfig {
@Bean
public CircuitBreakerRegistry circuitBreakerRegistry() {
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.slidingWindowSize(10)
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofSeconds(5))
.permittedNumberOfCallsInHalfOpenState(3)
.build();
return CircuitBreakerRegistry.of(config);
}
@PostConstruct
public void postConstruct(CircuitBreakerRegistry registry) {
CircuitBreaker circuitBreaker = registry.circuitBreaker("productService");
circuitBreaker.getEventPublisher()
.onStateTransition(event -> {
log.info("Circuit Breaker state changed from {} to {}",
event.getStateTransition().getFromState(),
event.getStateTransition().getToState());
});
}
}
3. Gestion des Erreurs
3.1 Exceptions Personnalisées
public class ProductNotFoundException extends RuntimeException {
public ProductNotFoundException(Long id) {
super("Product not found with id: " + id);
}
}
public class ServiceUnavailableException extends RuntimeException {
public ServiceUnavailableException(String message) {
super(message);
}
}
3.2 Gestionnaire d’Erreurs Global
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ProductNotFoundException.class)
public ResponseEntity<ErrorResponse> handleProductNotFound(ProductNotFoundException ex) {
ErrorResponse response = new ErrorResponse(
"PRODUCT_NOT_FOUND",
ex.getMessage()
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
}
@ExceptionHandler(ServiceUnavailableException.class)
public ResponseEntity<ErrorResponse> handleServiceUnavailable(ServiceUnavailableException ex) {
ErrorResponse response = new ErrorResponse(
"SERVICE_UNAVAILABLE",
ex.getMessage()
);
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(response);
}
}
4. Monitoring et Métriques
4.1 Configuration Actuator
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
4.2 Métriques Personnalisées
@Component
public class CircuitBreakerMetrics {
private final MeterRegistry registry;
private final CircuitBreaker circuitBreaker;
public CircuitBreakerMetrics(MeterRegistry registry, CircuitBreaker circuitBreaker) {
this.registry = registry;
this.circuitBreaker = circuitBreaker;
registerMetrics();
}
private void registerMetrics() {
Gauge.builder("circuit.breaker.state", circuitBreaker, this::getStateValue)
.description("Circuit breaker state")
.register(registry);
Counter.builder("circuit.breaker.calls")
.description("Number of calls")
.tag("result", "success")
.register(registry);
Counter.builder("circuit.breaker.calls")
.description("Number of calls")
.tag("result", "failure")
.register(registry);
}
private double getStateValue(CircuitBreaker cb) {
switch (cb.getState()) {
case CLOSED: return 0;
case OPEN: return 1;
case HALF_OPEN: return 2;
default: return -1;
}
}
}
5. Tests
5.1 Test Unitaire
@SpringBootTest
class ProductServiceTest {
@Autowired
private ProductService productService;
@MockBean
private ProductRepository productRepository;
@Test
void whenCircuitBreakerIsOpen_thenFallbackIsReturned() {
// Arrange
when(productRepository.findById(1L))
.thenThrow(new RuntimeException("Service unavailable"));
// Act
Product product = productService.getProduct(1L);
// Assert
assertEquals("Fallback Product", product.getName());
assertEquals(BigDecimal.ZERO, product.getPrice());
}
}
5.2 Test d’Intégration
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class CircuitBreakerIntegrationTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
void whenServiceIsUnavailable_thenCircuitBreakerIsTriggered() {
// Act
ResponseEntity<Product> response = restTemplate.getForEntity(
"/api/products/1", Product.class);
// Assert
assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody());
assertEquals("Fallback Product", response.getBody().getName());
}
}
Conclusion
Le Circuit Breaker avec Resilience4J offre plusieurs avantages :
- Protection contre les pannes en cascade
- Gestion automatique des états (ouvert, fermé, semi-ouvert)
- Métriques et monitoring intégrés
- Fallback automatique en cas d’échec
- Configuration flexible et extensible
Dans le prochain tutoriel, nous verrons comment implémenter le tracing distribué avec Sleuth et Zipkin pour suivre les requêtes à travers nos microservices.
Commentaires
Les commentaires sont alimentés par GitHub Discussions
Connectez-vous avec GitHub pour participer à la discussion