Zum Inhalt springen

Circuit-Breaker-Pattern: Zustände, Schwellenwerte, Retries und Fallbacks

Wie ein Circuit Breaker kaskadierende Ausfälle stoppt: geschlossene, offene und halboffene Zustände, sinnvolle Schwellenwerte, Timeouts, Retries und gute Fallbacks.

· Gründer & Entwickler

Veröffentlicht am

Zustandsdiagramm mit drei Zuständen. Closed wechselt zu Open, wenn die Fehlerquote 50 Prozent der letzten 100 Aufrufe erreicht. Open wechselt nach 60 Sekunden zu Half-open. Half-open kehrt bei erfolgreichen Testaufrufen zu Closed zurück, bei einem Fehler zu Open. Darunter ein Aufrufpfad.

Ein Zahlungsanbieter antwortet plötzlich in zehn Sekunden statt in fünfzig Millisekunden. Dein Checkout-Service ruft ihn weiter auf, jeder Request blockiert einen Worker, während er wartet, und nach einer Minute hat der Checkout keine freien Worker mehr, auch nicht für Requests, die den Zahlungsanbieter nie gebraucht hätten. Dann laufen auch die Services, die den Checkout aufrufen, in Timeouts. Aus einer langsamen Abhängigkeit ist ein Ausfall über mehrere Teams geworden.

Ein Circuit Breaker unterbricht diese Kette, indem er eine Abhängigkeit, die offensichtlich ausfällt, eine Zeit lang nicht mehr aufruft und stattdessen sofort antwortet. Dieser Beitrag behandelt die drei Zustände mit einem Zustandsdiagramm, die Schwellenwerte, die entscheiden, wann der Breaker auslöst, wie er mit Timeouts, Retries und Bulkheads zusammenspielt, was einen guten Fallback ausmacht und wo der Breaker hingehört: in deinen Code oder in ein Service Mesh.

Warum Ausfälle kaskadieren

Die Rechnung ist einfach und gnadenlos. Ein Service mit 200 Worker-Threads, der eine Abhängigkeit mit 50 ms Antwortzeit aufruft, schafft über diesen Pfad etwa 4.000 Requests pro Sekunde. Verlangsamt sich die Abhängigkeit bis zu einem Timeout von 10 Sekunden, schaffen dieselben 200 Threads 20 Requests pro Sekunde. In deinem Service ist nichts kaputt; seine Kapazität wurde einfach vom Warten aufgefressen.

Retries machen es schlimmer. Wenn drei Service-Schichten jeweils dreimal wiederholen, kann aus einem Benutzer-Request 27 Aufrufe an die angeschlagene Abhängigkeit ganz unten werden, genau in dem Moment, in dem sie sie am wenigsten verkraftet.

Michael Nygard beschrieb den Circuit Breaker 2007 in seinem Buch darüber, Software für den Produktionsbetrieb zu bauen, und Martin Fowlers Beschreibung machte ihn zu einem Standardbaustein des Service-Designs: Aufrufe an eine Abhängigkeit kapseln, Fehler zählen und bei Überschreiten einer Schwelle eine Zeit lang nicht mehr aufrufen.

Die drei Zustände

Zustand Was mit Aufrufen passiert Wechselt zu
Geschlossen (Closed) Aufrufe gehen durch; Ergebnisse werden in einem gleitenden Fenster erfasst Offen, wenn die Fehler- oder Slow-Call-Quote die Schwelle überschreitet
Offen (Open) Aufrufe scheitern sofort, ohne die Abhängigkeit zu berühren; der Fallback antwortet Halboffen, nach einer Wartezeit
Halboffen (Half-open) Eine kleine Zahl von Testaufrufen geht durch; der Rest scheitert weiter sofort Geschlossen bei erfolgreichen Tests, offen bei Fehlschlag

Der offene Zustand erledigt zwei Aufgaben gleichzeitig. Er schützt deinen Service, weil sofortiges Scheitern Worker sofort freigibt. Und er schützt die Abhängigkeit, weil sie eine Verkehrspause bekommt, während sie sich erholt, statt eines wachsenden Bergs von Retries.

Halboffen gibt es, weil der Breaker nicht wissen kann, ob die Abhängigkeit wieder gesund ist, ohne es zu versuchen. Eine Handvoll Aufrufe durchzulassen, nicht den ganzen Verkehr, testet die Erholung, ohne die Abhängigkeit in dem Moment umzuwerfen, in dem sie zurückkommt.

Was als Fehler zählt

Entscheide das bewusst, denn die falsche Wahl öffnet Breaker aus den falschen Gründen:

  • Zählen: Timeouts, Verbindungsfehler, 5xx-Antworten und Aufrufe, die langsamer sind als deine Slow-Call-Schwelle.
  • Nicht zählen: 4xx-Antworten wie 400 oder 404. Sie beschreiben einen fehlerhaften Request, keine ausfallende Abhängigkeit, und ein einzelner sich falsch verhaltender Client sollte nicht alle anderen abschneiden.
  • Im Einzelfall entscheiden: 429 Too Many Requests. Das heißt, die Abhängigkeit schützt sich selbst, was oft ein guter Grund ist, sich zurückzunehmen.

Langsame Aufrufe zählen genauso wie Fehler. Eine Abhängigkeit, die jeden Request erfolgreich in acht Sekunden beantwortet, ist für eine interaktive API eine ausfallende Abhängigkeit.

Schwellenwerte wählen

Resilience4j, eine weit verbreitete Breaker-Bibliothek auf der JVM, kommt mit konservativen Standardwerten, die man kennen sollte, weil viele Teams sie nie ändern:

Einstellung Standard Bedeutung
failureRateThreshold 50 % Öffnen, wenn die Hälfte der erfassten Aufrufe scheiterte
slidingWindowType / slidingWindowSize Zählbasiert, 100 Die letzten 100 Aufrufe beurteilen
minimumNumberOfCalls 100 Erst urteilen, wenn 100 Aufrufe erfasst sind
slowCallDurationThreshold 60.000 ms Langsamere Aufrufe gelten als langsam
slowCallRateThreshold 100 % Nur öffnen, wenn jeder Aufruf langsam ist
waitDurationInOpenState 60.000 ms Eine Minute offen bleiben
permittedNumberOfCallsInHalfOpenState 10 Zehn Testaufrufe entscheiden über die Erholung

Für eine interaktive API sind die Slow-Call-Standards praktisch ausgeschaltet: Niemand wartet 60 Sekunden auf eine Antwort. Vernünftige Startpunkte sind eine Slow-Call-Schwelle nahe deinem Latenzziel, eine Slow-Call-Quote von 50 % bis 80 % und eine Wartezeit im offenen Zustand von 10 bis 30 Sekunden. Halte minimumNumberOfCalls aussagekräftig: Auf einem Endpunkt mit wenig Verkehr sind zwei Fehler bei drei Aufrufen Rauschen, kein Ausfall. Zeitbasierte Fenster verhalten sich besser als zählbasierte, wenn der Verkehr über den Tag stark schwankt.

Timeouts, Retries und Bulkheads

Ein Circuit Breaker ist eines von vier Werkzeugen, die nur zusammen gut funktionieren:

  • Timeouts kommen zuerst. Ohne Timeout scheitert ein langsamer Aufruf nie, also sieht der Breaker nichts zum Zählen. Setz jeden Timeout unter den Timeout des Aufrufers und reich Deadlines entlang der Aufrufkette weiter, damit niemand an einem Request weiterarbeitet, der längst aufgegeben wurde.
  • Retries brauchen Grenzen. Wiederhole nur idempotente Operationen, nutze exponentielles Backoff mit Jitter, wie im AWS Architecture Blog beschrieben, wiederhole auf einer Schicht statt auf jeder und begrenze Retries auf einen Anteil des Verkehrs. Die Retry-Budgets von Envoy liegen standardmäßig bei 20 % der aktiven Requests, wenn du sie aktivierst.
  • Wiederhole nie gegen einen offenen Breaker. Resilience4j legt den Retry standardmäßig außen um den Breaker, also läuft jeder Versuch durch ihn hindurch; konfiguriere Retries so, dass sie beim Fehler „call not permitted“ aufhören, statt auf einen Breaker einzuhämmern, der schon entschieden hat.
  • Bulkheads isolieren. Gib jeder Abhängigkeit einen eigenen Connection-Pool oder ein eigenes Concurrency-Limit, damit ein langsamer Zahlungsanbieter nur seinen eigenen Pool aufbrauchen kann, nicht jeden Worker im Service.

Fallbacks, die Sinn ergeben

Wenn der Breaker offen ist, muss irgendetwas antworten. Der richtige Fallback hängt davon ab, wofür die Daten da sind:

Funktion Guter Fallback Schlechter Fallback
Empfehlungen Beliebte Artikel oder den Bereich ausblenden Den Empfehlungsdienst in einer Schleife erneut aufrufen
Benutzer-Avatar Ein Platzhalterbild Die Seite blockieren, bis der Bilddienst antwortet
Lieferzeitschätzung Eine gecachte Schätzung mit Hinweis Ein erfundenes Lieferdatum
Preis oder Zahlung Ein klarer Fehler und ein Weg, es erneut zu versuchen Ein geschätzter Preis oder eine stillschweigend übersprungene Abbuchung

Ein Fallback muss billig sein und darf nicht von dem abhängen, was gerade ausgefallen ist. Wenn die ehrliche Antwort „das funktioniert gerade nicht“ lautet, scheitere schnell mit einem klaren Fehler; das ist immer noch viel besser als ein Timeout.

Wo der Breaker hingehört

In-Process-Bibliothek Service Mesh oder Proxy
Beispiele Resilience4j (Java), Polly (.NET), gobreaker (Go), opossum (Node.js) Envoy Circuit Breaking und Outlier Detection
Sieht Exceptions, fachliche Fehler, Fallbacks Verbindungen, Requests, Antwortcodes
Fallbacks Ja, in deinem Code Nein; der Aufrufer bekommt einen Fehler
Einheitlichkeit über Teams Jedes Team konfiguriert selbst Eine Richtlinie, überall angewendet

Envoys Modell lohnt einen genauen Blick, weil es vom Lehrbuchmodell abweicht. Seine Circuit Breaker sind Concurrency-Limits pro Upstream-Cluster, standardmäßig 1.024 Verbindungen, 1.024 ausstehende Requests, 1.024 aktive Requests und 3 gleichzeitige Retries. Fehlererkennung pro Host ist ein separates Feature, Outlier Detection, das einen Host standardmäßig nach fünf aufeinanderfolgenden 5xx-Antworten für 30 Sekunden auswirft, bei wiederholtem Auswerfen länger, und nie mehr als 10 % eines Clusters auswirft. Das Service-Mesh-Architekturdiagramm zeigt, wo diese Proxies sitzen.

Viele Teams nutzen beides: das Mesh für einheitliche Limits und das Auswerfen schlechter Hosts, und eine Bibliothek dort, wo die Anwendung einen echten Fallback braucht. Netflix' Hystrix, das das Muster populär gemacht hat, ist seit 2018 im Wartungsmodus; Resilience4j ist auf der JVM der übliche Ersatz.

Beobachten und testen

Ein Breaker, der still öffnet, macht aus einem Ausfall ein Rätsel. Gib jeden Zustandswechsel als Log-Event und als Metrik aus, alarmiere, wenn ein Breaker offen bleibt, und zeig Breaker-Zustände auf demselben Dashboard wie die Latenz der Abhängigkeit. Dann teste es: Injiziere in einer Staging-Umgebung Latenz und Fehler in die Abhängigkeit und beobachte, wie der Breaker öffnet, der Fallback antwortet und der Breaker wieder schließt. Das Observability-Architekturdiagramm behandelt die Seite mit Metriken und Tracing.

Das Circuit-Breaker-Diagramm

Zustandsdiagramm mit drei Zuständen. Closed wechselt zu Open, wenn die Fehlerquote 50 Prozent der letzten 100 Aufrufe erreicht. Open wechselt nach 60 Sekunden zu Half-open. Half-open kehrt bei erfolgreichen Testaufrufen zu Closed zurück, bei einem Fehler zu Open. Darunter ein Aufrufpfad.

11 Komponenten · 7 Verbindungen

In Archboard öffnen
Diagramminhalt
  • 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
Ein Circuit Breaker als Zustandsautomat. Geschlossen lässt Aufrufe durch und misst Fehler; offen scheitert sofort und liefert einen Fallback; halboffen lässt einige Testaufrufe durch, um zu prüfen, ob sich die Abhängigkeit erholt hat.

So liest du es:

  • Drei Zustände. Geschlossen ist der Normalzustand, offen scheitert sofort, halboffen testet die Erholung. Der Punkt markiert den Startzustand.
  • Übergänge tragen ihre Bedingungen: 50 % Fehlerquote über die letzten 100 Aufrufe öffnen den Breaker, 60 Sekunden Wartezeit bringen ihn nach halboffen, und das Ergebnis der Testaufrufe schließt oder öffnet ihn erneut.
  • Der Aufrufpfad unten zeigt den Aufrufer, den Zahlungsanbieter hinter dem Breaker und den Fallback, der antwortet, solange der Breaker offen ist.
  • Der Markdown-Block listet die Einstellungen mit ihren Standardwerten.

Resilienzmuster klar zeichnen

  • Zeichne den Breaker als Zustandsautomaten. Beschreibungen in Fließtext verwischen den Unterschied zwischen offen und halboffen; ein Diagramm macht ihn offensichtlich.
  • Schreib die Zahlen an die Übergänge. „Öffnet bei Fehlern“ lädt zur Diskussion ein; „öffnet bei 50 % der letzten 100 Aufrufe“ lädt zum Review ein.
  • Zeig den Fallback-Pfad, einschließlich dessen, was der Benutzer sieht.
  • Trag Timeouts und Retries ins Aufrufpfad-Diagramm ein, damit Reviewer Retries über Schichten hinweg multiplizieren können.

Mehr zur Notation von Zustandsautomaten findest du im Zustandsdiagramm-Beispiel. Das Microservices-Architekturdiagramm zeigt, wo Breaker zwischen Services sitzen.

In Archboard nachbauen

  1. Melde dich mit Google oder GitHub an und leg ein Diagramm an.
  2. Füge drei Zustand-Nodes aus dem UML-Pack hinzu: Closed, Open und Half-open. Färbe sie grün, rot und bernsteinfarben.
  3. Füge eine kleine Form in der Variante Ellipse aus dem Basis-Pack als Startzustand hinzu und verbinde sie mit Closed.
  4. Zeichne die Übergänge als beschriftete Kanten: die Fehlerschwelle, die Wartezeit, Erfolg und Fehlschlag der Testaufrufe. Füge neben jedem Zustand einen Text aus dem Basis-Pack hinzu, der sagt, was dort mit Aufrufen passiert.
  5. Füge unter dem Zustandsautomaten einen Kasten aus dem Basis-Pack für den Aufrufer, ein Externes System aus dem C4-Modell-Pack für den Zahlungsanbieter und einen weiteren Kasten für den Fallback hinzu, mit einer gestrichelten Kante für den Pfad im offenen Zustand.
  6. Füge einen Markdown-Block aus dem Basis-Pack mit den Einstellungen und Standardwerten hinzu.

Das Beispiel nutzt 8 Diagramm-Nodes und bleibt damit im Free-Plan.

FAQ

Was sind die drei Zustände eines Circuit Breakers? Geschlossen, in dem Aufrufe durchgehen und Fehler gezählt werden; offen, in dem Aufrufe für eine Wartezeit sofort scheitern; und halboffen, in dem einige Testaufrufe entscheiden, ob der Breaker wieder schließt oder erneut öffnet.

Was ist der Unterschied zwischen einem Circuit Breaker und einem Retry? Ein Retry wiederholt einen fehlgeschlagenen Aufruf in der Hoffnung, dass er gelingt. Ein Circuit Breaker stoppt Aufrufe ganz, wenn Fehler häufig sind. Werden beide kombiniert, sollten Retries begrenzt sein und aufhören, wenn der Breaker offen ist.

Sollten 4xx-Fehler einen Circuit Breaker öffnen? Meist nicht. Ein 400 oder 404 bedeutet, dass der Request falsch war, nicht dass die Abhängigkeit ausgefallen ist. Zähl stattdessen Timeouts, Verbindungsfehler, 5xx-Antworten und langsame Aufrufe.

Brauche ich einen Circuit Breaker, wenn ich ein Service Mesh nutze? Ein Mesh gibt dir Concurrency-Limits und das Auswerfen schlechter Hosts ohne Codeänderungen. Einen In-Process-Breaker brauchst du trotzdem dort, wo du einen echten Fallback willst, etwa einen gecachten Wert oder eine eingeschränkte Funktion.

Was ist eine gute Wartezeit für den offenen Zustand? Lang genug, damit sich eine angeschlagene Abhängigkeit erholen kann, und kurz genug, dass Benutzer danach keinen langen Ausfall sehen. Zehn bis dreißig Sekunden sind für interaktive APIs ein üblicher Startpunkt.

Kostenlos starten — ein Seat, keine Kreditkarte.

Ähnliche Beiträge

← Zurück zum Blog

In einer Minute ein Canvas öffnen

Melden Sie sich mit Google oder GitHub an. Kostenlos für einen Sitzplatz. Upgraden Sie, wenn das Team dazukommt.