Le pattern Circuit Breaker : échouer vite pour ne pas échouer lentement
Dans une architecture microservices, une dépendance en difficulté peut entraîner tout le système avec elle — le circuit breaker empêche cette propagation avant qu'elle ne commence.
Les microservices s'appellent constamment entre eux. Le circuit breaker existe parce que tôt ou tard, l'un de ces appels échouera — et la vraie question est jusqu'où cet échec est autorisé à se propager.
Le problème qu'il résout
Quand un service en aval ralentit ou échoue, les appelants s'accumulent en attente : threads, connexions, mémoire. Réessayer aveuglément peut aggraver les choses, puisqu'un service incapable de se rétablir se fait simplement marteler davantage. Parfois, la décision la plus saine est d'échouer vite plutôt que d'attendre un échec certain.
Trois états, une seule machine à états
- Fermé (Closed) — les requêtes passent normalement, et les échecs sont suivis sur une fenêtre glissante.
- Ouvert (Open) — une fois le seuil d'échecs dépassé, les requêtes cessent d'atteindre le service immédiatement, pour une durée définie.
- Semi-ouvert (Half-open) — après cette durée, un nombre limité de requêtes test sont envoyées. Un succès referme le circuit ; un échec le rouvre.
Les fallbacks rendent l'échec supportable
Un circuit ouvert sans fallback ne fait que remplacer une erreur par une autre. Un fallback renvoie une réponse par défaut ou dégradée à la place, pour que l'appelant — et l'utilisateur final — reçoive quelque chose d'exploitable plutôt qu'un échec brut.
Un circuit par dépendance, pas par méthode
Plusieurs méthodes appelant le même service en aval devraient partager une seule instance de circuit breaker, puisque ce qui est mesuré est la santé de ce service, pas celle d'une méthode isolée. Chaque méthode peut néanmoins définir son propre fallback, adapté à ce qu'elle fait.
La messagerie asynchrone est un outil différent
Les files comme Kafka découplent l'appelant et l'appelé pour qu'un service lent ne bloque pas la requête — mais elles ne suppriment pas l'échec, elles le déplacent, vers du retard de consommation ou de la contre-pression (backpressure). L'asynchrone convient aux appels qui n'ont pas besoin de réponse immédiate ; les appels synchrones qui en ont besoin nécessitent toujours un circuit breaker.
Avec Spring Boot
Resilience4j est le choix standard, appliqué via @CircuitBreaker(name, fallbackMethod) et paramétré par configuration : taille de la fenêtre glissante, seuil de taux d'échec, durée d'attente à l'état ouvert, et nombre d'appels autorisés en semi-ouvert. Il se combine bien avec @Retry, @RateLimiter et @Bulkhead pour une stratégie de résilience plus complète.
La leçon
Un circuit breaker n'empêche pas l'échec. Il le contient — transformant une dépendance en difficulté en un cas géré plutôt qu'en une panne généralisée du système.