Circuit breaker et bulkhead¶
Deux patterns complementaires pour contenir les pannes — l'un coupe, l'autre isole.
Circuit breaker¶
Le circuit breaker protégé un service appelant quand un service appelé devient defaillant. Sans lui, chaque requête attendrait un timeout complet avant d'échouer — epuisant les threads, les connexions, et propageant la panne en cascade vers tous les appelants.
Le pattern emprunte son nom aux disjoncteurs électriques : quand la charge est trop forte, on coupe le circuit pour protéger l'installation. On le retablit ensuite progressivement.
stateDiagram-v2
[*] --> Closed
Closed --> Open : seuil d'erreurs atteint
Open --> HalfOpen : timer expire
HalfOpen --> Closed : sondes reussies
HalfOpen --> Open : sonde echouee
Closed : Closed\nRequetes passent normalement
Open : Open\nRequetes bloquees — fallback immediat
HalfOpen : Half-Open\nQuelques requetes de sonde États et transitions¶
Closed — état normal. Les requêtes passent. Le circuit compte les succès et les échecs dans une fenêtre glissante.
Open — le seuil d'échec a été dépassé. Les requêtes sont bloquees immédiatement, sans appeler la dépendance. Le fallback est retourné. Cet état persiste pendant la durée du timeout configuré.
Half-Open — après expiration du timer, quelques requêtes de sonde sont laissees passer pour tester si la dépendance est retablie. Si elles reussissent, le circuit se referme. Si elles échouent, il reste ouvert et le timer recommence.
Parametres clés¶
| Parametre | Exemple | Rôle |
|---|---|---|
| Error threshold | 50% sur 10 requêtes | Seuil d'ouverture du circuit |
| Minimum requests | 10 requêtes | Taille de la fenêtre de mesure |
| Timeout (open) | 30s | Durée en état Open avant de tenter Half-Open |
| Half-open probes | 3 requêtes | Nombre de sondes reussies pour refermer |
Stratégies de fallback¶
Quand le circuit est ouvert, le service doit répondre sans appeler la dépendance defaillante. Trois stratégies :
- Cache — retourner la dernière réponse connue si elle est acceptable (produits populaires, solde mis en cache)
- Valeur par défaut — retourner une liste vide, un score neutre, un message générique qui préservé l'expérience de base
- Message d'erreur explicite — informer l'utilisateur proprement avec un message utile plutôt que de le faire attendre indéfiniment
Exemple concret : le service de recommandation tombe. Après 10 requêtes dont 60% ont échoué, le circuit s'ouvre. En fallback, l'application affiche les produits les plus populaires de la semaine, issus d'un cache Redis. L'utilisateur voit quelque chose d'utile. Après 30 secondes, le circuit passe en Half-Open. Une requête de sonde est envoyee. Si le service répond, le circuit se referme et les recommandations personnalisees reviennent.
Library-based vs sidecar-based¶
Le circuit breaker peut être implémenté de deux manières fondamentalement différentes, chacune avec ses forces et ses contraintes.
Implémentation par bibliotheque¶
Le circuit breaker est intégré dans le code applicatif via une bibliotheque. Le développeur configure les seuils, les fallbacks et la logique de transition directement dans le service.
| Bibliotheque | Langage | Statut | Notes |
|---|---|---|---|
| Resilience4j | Java | Actif | Successeur de Hystrix, léger, modulaire |
| Polly | .NET | Actif | Intégration native avec HttpClientFactory |
| opossum | Node.js | Actif | Simple, bien maintenu |
| gobreaker | Go | Actif | Implémentation Sony, minimaliste |
| Hystrix | Java | Deprecie | Pionnier Netflix, plus maintenu |
Avantages : contrôle fin sur les fallbacks, accès au contexte métier (le fallback peut retourner des données métier pertinentes), pas de composant d'infrastructure supplémentaire.
Inconvénients : chaque service doit implémenter le pattern, risque d'inconsistance entre équipes, pas de vision globale de l'état des circuits.
Implémentation par sidecar (service mesh)¶
Le circuit breaker est géré par un proxy sidecar (Envoy, Linkerd proxy) qui intercepte tout le trafic réseau du service. La configuration est declarative et centralisee.
# Istio DestinationRule — circuit breaker via Envoy
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: catalogue-circuit-breaker
spec:
host: catalogue-service
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
h2UpgradePolicy: DEFAULT
http1MaxPendingRequests: 50
http2MaxRequests: 100
outlierDetection:
consecutive5xxErrors: 5
interval: 30s
baseEjectionTime: 30s
maxEjectionPercent: 50
Avantages : uniforme sur tous les services (polyglotte), pas de code applicatif, configuration centralisee, métriques automatiques sur l'état des circuits.
Inconvénients : fallback limite (le sidecar ne connait pas le contexte métier), complexité opérationnelle du mesh, latence ajoutee par le proxy.
Choix pragmatique¶
| Critère | Library | Sidecar |
|---|---|---|
| Fallback métier riche | Oui | Limite |
| Polyglotte | Non (par langage) | Oui |
| Cohérence entre équipes | Difficile | Automatique |
| Complexité opérationnelle | Faible | Élevée (mesh) |
| Visibilité centralisee | A construire | Native |
En pratique, les deux approches coexistent souvent. Le service mesh fournit une protection de base (outlier détection, ejection) et le code applicatif ajoute des fallbacks métier là où c'est nécessaire.
Monitoring de l'état des circuits¶
Un circuit breaker invisible est un circuit breaker dangereux. Si personne ne sait qu'un circuit est ouvert, le fallback peut durer des heures sans que l'équipe n'en soit informee.
Métriques a exposer¶
Chaque circuit breaker doit émettre des métriques :
- État courant (gauge) — 0 = closed, 1 = half-open, 2 = open
- Nombre de transitions (counter) — ouvertures et fermetures par fenêtre de temps
- Taux de rejet (counter) — requêtes bloquees par le circuit ouvert
- Durée en état open (histogram) — combien de temps le circuit reste ouvert
Alerting¶
Deux alertes minimales :
- Circuit ouvert depuis plus de X minutes — un circuit ouvert longtemps signale un problème persistant qui nécessité une intervention
- Oscillation rapide (flapping) — le circuit s'ouvre et se ferme plusieurs fois par minute, signe d'une dépendance instable en limite de seuil
graph LR
CB["Circuit Breaker"] -->|metriques| Prom["Prometheus"]
Prom -->|regles| AM["Alertmanager"]
AM -->|notification| On["On-call"]
Prom -->|visualisation| Graf["Grafana Dashboard"]
style CB fill:#264653,color:#fff
style Prom fill:#2a9d8f,color:#fff Bulkhead¶
Le pattern bulkhead (cloison etanche) isole les ressources par service dependant pour limiter la propagation des pannes. Le nom vient de l'architecture navale : des compartiments etanches empêchent qu'une seule voie d'eau coule tout le bateau.
graph TD
App[Application]
App --> P1[Pool A — Paiement\n10 threads / 20 connexions]
App --> P2[Pool B — Catalogue\n10 threads / 20 connexions]
App --> P3[Pool C — Recommandation\n5 threads / 10 connexions]
P1 --> SvcA[Service Paiement]
P2 --> SvcB[Service Catalogue]
P3 --> SvcC[Service Recommandation\nEN PANNE]
style P3 fill:#c44,color:#fff
style SvcC fill:#c44,color:#fff Principe¶
Un service lent ou bloque ne doit pas épuiser toutes les ressources de l'application et faire tomber les autres. Sans bulkhead, si le service de recommandation répond en 30 secondes, ses requêtes occupent des threads en attente. En quelques minutes, le pool global de threads est sature. Plus aucune requête — y compris les paiements critiques — ne peut être traitee. Le service de recommandation a contamine tout le système.
Le bulkhead garantit que chaque service dependant ne peut consommer qu'un sous-ensemble défini des ressources. Si le pool du service de recommandation est plein, seules les requêtes de recommandation sont bloquees. Les paiements continuent à fonctionner normalement.
Types d'isolation¶
Thread pool isolation — chaque dépendance a son propre pool de threads. Modèle popularise par Hystrix (Netflix). Le thread appelant retourné immédiatement si le pool est plein (rejet plutôt que blocage). Coûteux en mémoire mais très efficace.
Semaphore isolation — limite le nombre de requêtes concurrentes par dépendance via un compteur. Plus léger qu'un pool de threads, mais le thread appelant reste bloque si la limite est atteinte. Adapté aux appels rapides.
Connection pool isolation — chaque dépendance a son quota de connexions dans un pool partage mais borne. Utile pour les connexions base de données ou les clients HTTP.
Process isolation — chaque dépendance est appelée via un sidecar ou un processus dédié. Plus lourd, mais isole aussi les pannes mémoire et CPU. Modèle utilisé dans les service meshes (Envoy proxy).
Dimensionner les pools¶
Le dimensionnement des bulkheads est un exercice d'équilibre :
- Trop petit — les requêtes sont rejetees en conditions normales, degradant le service sans raison
- Trop grand — la protection est illusoire car le pool ne sera jamais plein avant que les degats ne se propagent
Méthode pragmatique :
- Mesurer le throughput normal de chaque dépendance (requêtes/seconde)
- Mesurer la latence p99 de chaque dépendance
- Calculer la concurrence maximale :
throughput * latence_p99 - Ajouter une marge de 50% pour les pics
- Fixer le pool a cette valeur et monitorer les rejets
Bulkhead et circuit breaker : deux couches complementaires¶
Les deux patterns protègent contre la propagation des pannes mais a des niveaux différents :
| Situation | Bulkhead | Circuit breaker |
|---|---|---|
| Service lent (pas encore en erreur) | Limite les ressources consommees | Pas encore déclenché |
| Service en erreur franche | Limite les ressources gaspillees | Coupe les appels, active fallback |
| Retour à la normale | Pool se vide naturellement | Half-open puis closed |
Un service lent peut saturer un pool (le bulkhead agit) sans atteindre le seuil d'erreur (le circuit breaker n'a pas encore agi). Déployer les deux ensemble pour une protection complète.
graph TD
Req["Requete entrante"]
BH{"Bulkhead\nPool disponible ?"}
CB{"Circuit breaker\nCircuit ferme ?"}
Call["Appel dependance"]
FB["Fallback"]
Req --> BH
BH -->|oui| CB
BH -->|non — pool plein| FB
CB -->|ferme| Call
CB -->|ouvert| FB
style FB fill:#e76f51,color:#fff
style Call fill:#2a9d8f,color:#fff La sequence est : le bulkhead agit en premier (le pool a-t-il de la place ?), puis le circuit breaker décidé (le circuit est-il ferme ?). Si les deux laissent passer, la requête atteint la dépendance. Si l'un des deux bloque, le fallback est retourné immédiatement.
Chapitre suivant : Retry et backoff — retenter intelligemment sans amplifier les pannes.