Aller au contenu

Pattern circuit breaker : états, seuils, retries et solutions de repli

Comment un circuit breaker stoppe les pannes en cascade : états fermé, ouvert et semi-ouvert, seuils réalistes, timeouts, retries et bonnes solutions de repli.

· Fondateur et développeur

Publié le

Diagramme d'états à trois états. Closed passe à Open quand le taux d'échec atteint 50 % des 100 derniers appels. Open passe à Half-open après 60 secondes d'attente. Half-open revient à Closed si les appels d'essai réussissent, ou à Open si l'un échoue. Un chemin d'appel figure dessous.

Un prestataire de paiement se met à répondre en dix secondes au lieu de cinquante millisecondes. Votre service de checkout continue de l'appeler, chaque requête bloque un worker pendant l'attente, et en une minute le checkout n'a plus aucun worker libre, y compris pour des requêtes qui n'avaient pas besoin du prestataire de paiement. Puis les services qui appellent le checkout commencent eux aussi à tomber en timeout. Une seule dépendance lente est devenue une panne qui touche plusieurs équipes.

Un circuit breaker arrête cette chaîne en refusant, pendant un temps, d'appeler une dépendance qui échoue manifestement, et en répondant immédiatement à la place. Cet article couvre les trois états avec un diagramme d'états, les seuils qui décident quand le breaker se déclenche, sa combinaison avec les timeouts, les retries et les bulkheads, ce qui fait une bonne solution de repli, et où placer le breaker : dans votre code ou dans un service mesh.

Pourquoi les pannes se propagent en cascade

Le calcul est simple et implacable. Un service doté de 200 threads de travail qui appelle une dépendance répondant en 50 ms peut traiter environ 4 000 requêtes par seconde sur ce chemin. Si la dépendance ralentit jusqu'à un timeout de 10 secondes, les mêmes 200 threads traitent 20 requêtes par seconde. Rien n'est cassé dans votre service ; sa capacité a simplement été consommée par l'attente.

Les retries aggravent les choses. Si trois couches de services réessaient chacune trois fois, une requête utilisateur peut se transformer en 27 appels à la dépendance en difficulté tout en bas, précisément au moment où elle peut le moins se le permettre.

Michael Nygard a décrit le circuit breaker en 2007 dans son livre sur la conception de logiciels prêts pour la production, et la description de Martin Fowler en a fait un élément standard de la conception de services : encapsuler les appels à une dépendance, compter les échecs et, lorsqu'ils dépassent un seuil, cesser d'appeler pendant un moment.

Les trois états

État Ce qui arrive aux appels Passe à
Fermé (Closed) Les appels passent ; les résultats sont enregistrés dans une fenêtre glissante Ouvert, quand le taux d'échecs ou d'appels lents dépasse le seuil
Ouvert (Open) Les appels échouent immédiatement sans toucher la dépendance ; la solution de repli répond Semi-ouvert, après un temps d'attente
Semi-ouvert (Half-open) Un petit nombre d'appels d'essai passent ; les autres échouent toujours immédiatement Fermé si les essais réussissent, ouvert s'ils échouent

L'état ouvert remplit deux fonctions à la fois. Il protège votre service, car échouer vite libère les workers instantanément. Il protège aussi la dépendance, qui obtient une pause dans le trafic pendant qu'elle se rétablit, au lieu d'une pile croissante de retries.

L'état semi-ouvert existe parce que le breaker ne peut pas savoir si la dépendance est de nouveau en bonne santé sans essayer. Laisser passer une poignée d'appels, et non tout le trafic, teste le rétablissement sans renverser la dépendance au moment même où elle revient.

Ce qui compte comme un échec

Décidez-le délibérément, car un mauvais choix ouvre les breakers pour de mauvaises raisons :

  • À compter : les timeouts, les erreurs de connexion, les réponses 5xx et les appels plus lents que votre seuil d'appel lent.
  • À ne pas compter : les réponses 4xx comme 400 ou 404. Elles décrivent une mauvaise requête, pas une dépendance défaillante, et un seul client qui se comporte mal ne devrait pas couper tout le monde.
  • À décider au cas par cas : 429 Too Many Requests. Cela signifie que la dépendance se protège, ce qui est souvent une bonne raison de lever le pied.

Les appels lents comptent autant que les erreurs. Une dépendance qui répond avec succès à chaque requête en huit secondes est, pour une API interactive, une dépendance défaillante.

Choisir les seuils

Resilience4j, une bibliothèque de breaker très répandue sur la JVM, est livrée avec des valeurs par défaut prudentes qu'il vaut la peine de connaître, car beaucoup d'équipes ne les changent jamais :

Paramètre Défaut Signification
failureRateThreshold 50 % Ouvrir quand la moitié des appels enregistrés ont échoué
slidingWindowType / slidingWindowSize Par nombre, 100 Juger sur les 100 derniers appels
minimumNumberOfCalls 100 Ne pas juger avant que 100 appels soient enregistrés
slowCallDurationThreshold 60 000 ms Les appels plus lents comptent comme lents
slowCallRateThreshold 100 % N'ouvrir que si tous les appels sont lents
waitDurationInOpenState 60 000 ms Rester ouvert une minute
permittedNumberOfCallsInHalfOpenState 10 Dix appels d'essai décident du rétablissement

Pour une API interactive, les valeurs par défaut des appels lents reviennent à les désactiver : personne n'attend 60 secondes une réponse. Des points de départ raisonnables sont un seuil d'appel lent proche de votre objectif de latence, un taux d'appels lents de 50 % à 80 % et une attente à l'état ouvert de 10 à 30 secondes. Gardez minimumNumberOfCalls significatif : sur un point de terminaison peu sollicité, deux échecs sur trois appels sont du bruit, pas une panne. Les fenêtres temporelles se comportent mieux que les fenêtres par nombre quand le trafic varie beaucoup au cours de la journée.

Timeouts, retries et bulkheads

Un circuit breaker est l'un de quatre outils qui ne fonctionnent bien qu'ensemble :

  • Les timeouts d'abord. Sans timeout, un appel lent n'échoue jamais, donc le breaker ne voit rien à compter. Fixez chaque timeout en dessous de celui de l'appelant, et propagez les échéances le long de la chaîne d'appels pour que personne ne continue de travailler sur une requête déjà abandonnée.
  • Les retries ont besoin de limites. Ne réessayez que les opérations idempotentes, utilisez un backoff exponentiel avec jitter comme le décrit l'AWS Architecture Blog, réessayez à une seule couche plutôt qu'à toutes, et plafonnez les retries à une part du trafic. Les budgets de retry d'Envoy valent par défaut 20 % des requêtes actives quand vous les activez.
  • Ne réessayez jamais contre un breaker ouvert. Resilience4j applique par défaut le retry à l'extérieur du breaker, si bien que chaque tentative le traverse ; configurez les retries pour qu'ils s'arrêtent sur l'erreur « call not permitted » au lieu de marteler un breaker qui a déjà tranché.
  • Les bulkheads isolent. Donnez à chaque dépendance son propre pool de connexions ou sa propre limite de concurrence, pour qu'un prestataire de paiement lent ne puisse consommer que son pool, et non tous les workers du service.

Des solutions de repli qui ont du sens

Quand le breaker est ouvert, il faut bien que quelque chose réponde. La bonne solution de repli dépend de l'usage des données :

Fonctionnalité Bon repli Mauvais repli
Recommandations Des articles populaires, ou masquer la section Réessayer le service de recommandations en boucle
Avatar utilisateur Une image de remplacement Bloquer la page jusqu'à ce que le service d'images réponde
Estimation de livraison Une estimation en cache avec une mention Une date de livraison inventée
Prix ou paiement Une erreur claire et un moyen de réessayer Un prix deviné ou un débit sauté en silence

Une solution de repli doit être peu coûteuse et ne pas dépendre de ce qui vient d'échouer. Si la réponse honnête est « cela ne fonctionne pas pour l'instant », échouez vite avec une erreur claire ; c'est toujours bien mieux qu'un timeout.

Où placer le breaker

Bibliothèque dans le processus Service mesh ou proxy
Exemples Resilience4j (Java), Polly (.NET), gobreaker (Go), opossum (Node.js) Circuit breaking et outlier detection d'Envoy
Voit Exceptions, échecs métier, solutions de repli Connexions, requêtes, codes de réponse
Solutions de repli Oui, dans votre code Non ; l'appelant reçoit une erreur
Cohérence entre équipes Chaque équipe configure le sien Une seule politique, appliquée partout

Le modèle d'Envoy mérite d'être compris, car il diffère du modèle des manuels. Ses circuit breakers sont des limites de concurrence par cluster amont : par défaut 1 024 connexions, 1 024 requêtes en attente, 1 024 requêtes actives et 3 retries simultanés. La détection d'échecs par hôte est une fonctionnalité distincte, l'outlier detection, qui éjecte par défaut un hôte après cinq réponses 5xx consécutives pendant 30 secondes, plus longtemps en cas d'éjections répétées, et n'éjecte jamais plus de 10 % d'un cluster. Le diagramme d'architecture service mesh montre où se trouvent ces proxys.

Beaucoup d'équipes utilisent les deux : le mesh pour des limites cohérentes et l'éjection des mauvais hôtes, et une bibliothèque là où l'application a besoin d'une vraie solution de repli. Hystrix, de Netflix, qui a popularisé le pattern, est en mode maintenance depuis 2018 ; Resilience4j est son remplaçant habituel sur la JVM.

Observer et tester

Un breaker qui s'ouvre en silence transforme une panne en mystère. Émettez chaque changement d'état sous forme d'événement de log et de métrique, alertez quand un breaker reste ouvert, et affichez les états des breakers sur le même tableau de bord que la latence de la dépendance. Ensuite, testez : injectez de la latence et des erreurs dans la dépendance en préproduction et regardez le breaker s'ouvrir, la solution de repli répondre et le breaker se refermer. Le diagramme d'architecture d'observabilité couvre le volet métriques et traçage.

Le diagramme du circuit breaker

Diagramme d'états à trois états. Closed passe à Open quand le taux d'échec atteint 50 % des 100 derniers appels. Open passe à Half-open après 60 secondes d'attente. Half-open revient à Closed si les appels d'essai réussissent, ou à Open si l'un échoue. Un chemin d'appel figure dessous.

11 composants · 7 connexions

Ouvrir dans Archboard
Contenu du diagramme
  • CLOSED
  • OPEN
  • HALF-OPEN
  • Checkout service (caller)
  • Payment provider
  • Fallback: clear error, retry later
  • failure rate ≥ 50% of the last 100 calls
  • after a 60 s wait
  • a trial call fails
  • trial calls succeed
  • charge(), 2 s timeout, when closed or half-open
  • when open: no call, fail fast
Un circuit breaker vu comme une machine à états. Fermé laisse passer les appels et mesure les échecs ; ouvert échoue immédiatement et sert une solution de repli ; semi-ouvert laisse passer quelques appels d'essai pour tester si la dépendance s'est rétablie.

Comment le lire :

  • Trois états. Fermé est l'état normal, ouvert échoue vite, semi-ouvert teste le rétablissement. Le point marque l'état initial.
  • Les transitions portent leurs conditions : un taux d'échec de 50 % sur les 100 derniers appels ouvre le breaker, une attente de 60 secondes le fait passer en semi-ouvert, et le résultat des appels d'essai le referme ou le rouvre.
  • Le chemin d'appel en dessous montre l'appelant, le prestataire de paiement derrière le breaker et la solution de repli qui répond tant que le breaker est ouvert.
  • Le bloc Markdown liste les paramètres avec leurs valeurs par défaut.

Dessiner clairement les patterns de résilience

  • Dessinez le breaker comme une machine à états. Les descriptions en prose brouillent la différence entre ouvert et semi-ouvert ; un diagramme la rend évidente.
  • Écrivez les chiffres sur les transitions. « S'ouvre en cas d'échecs » invite au débat ; « s'ouvre à 50 % des 100 derniers appels » invite à la relecture.
  • Montrez le chemin de repli, y compris ce que voit l'utilisateur.
  • Placez timeouts et retries sur le diagramme du chemin d'appel, pour que les relecteurs puissent multiplier les retries d'une couche à l'autre.

Pour en savoir plus sur la notation des machines à états, voyez l'exemple de diagramme d'états. Le diagramme d'architecture microservices montre où se placent les breakers entre les services.

Le construire dans Archboard

  1. Connectez-vous avec Google ou GitHub et créez un diagramme.
  2. Ajoutez trois nœuds État du pack UML : Closed, Open et Half-open. Colorez-les en vert, rouge et ambre.
  3. Ajoutez une petite forme Ellipse du pack Base comme état initial et reliez-la à Closed.
  4. Dessinez les transitions comme des arêtes étiquetées : le seuil d'échec, le temps d'attente, le succès et l'échec des essais. Ajoutez à côté de chaque état un Texte du pack Base qui indique ce qui arrive aux appels.
  5. Sous la machine à états, ajoutez une Boîte du pack Base pour l'appelant, un Système externe du pack Modèle C4 pour le prestataire de paiement et une autre Boîte pour la solution de repli, avec une arête en pointillé pour le chemin de l'état ouvert.
  6. Ajoutez un bloc Markdown du pack Base avec les paramètres et leurs valeurs par défaut.

L'exemple utilise 8 nœuds de diagramme et reste dans les limites de l'offre Free.

FAQ

Quels sont les trois états d'un circuit breaker ? Fermé, où les appels passent et les échecs sont comptés ; ouvert, où les appels échouent immédiatement pendant un temps d'attente ; et semi-ouvert, où quelques appels d'essai décident de refermer le breaker ou de le rouvrir.

Quelle est la différence entre un circuit breaker et un retry ? Un retry répète un appel échoué dans l'espoir qu'il réussisse. Un circuit breaker arrête complètement les appels quand les échecs sont fréquents. Utilisés ensemble, les retries doivent être limités et s'arrêter quand le breaker est ouvert.

Les erreurs 4xx doivent-elles ouvrir un circuit breaker ? En général non. Un 400 ou un 404 signifie que la requête était erronée, pas que la dépendance a échoué. Comptez plutôt les timeouts, les erreurs de connexion, les réponses 5xx et les appels lents.

Ai-je besoin d'un circuit breaker si j'utilise un service mesh ? Un mesh vous donne des limites de concurrence et l'éjection des mauvais hôtes sans modifier le code. Il vous faut quand même un breaker dans le processus là où vous voulez une vraie solution de repli, comme une valeur en cache ou une fonctionnalité dégradée.

Quel est un bon temps d'attente pour l'état ouvert ? Assez long pour qu'une dépendance en difficulté se rétablisse, assez court pour que les utilisateurs ne subissent pas une longue panne une fois qu'elle est rétablie. Dix à trente secondes est un point de départ courant pour les API interactives.

Commencez gratuitement — un siège, sans carte bancaire.

Articles associés

← Retour au blog

Ouvrez un canevas en une minute

Connectez-vous avec Google ou GitHub. Gratuit pour un siège. Passez à un forfait supérieur quand l’équipe vous rejoint.