Stell dir vor: 3 Uhr morgens, dein Payment-Service geht down. Kein Drama, Services fallen aus, das passiert. Aber dann fängt der Rest deines Systems an, den Payment-Service weiter zu kontaktieren. Jeder Request wartet 30 Sekunden auf eine Antwort die nie kommt. Die Timeouts stapeln sich. Dein Thread-Pool läuft voll. Und plötzlich ist nicht nur der Payment-Service down — sondern alles.
Ein Service war kaputt. Dein ganzes System ist mitgegangen.
Glaubst du nicht? Kill den Payment Service und schau was passiert:
Links: ein Service stirbt und reißt alles mit. Die Latenz explodiert, der Throughput geht auf null. Rechts: gleiche Situation, aber der Breaker erkennt das Problem und kappt die Verbindung. Das System überlebt.
Das Pattern kommt aus der Elektrotechnik: Wenn zu viel Strom fließt, bricht der Sicherungsautomat den Kreislauf ab. Gleiche Idee, anderer Kontext.
Drei Zustände
Circuit Breaker ist ein Zustandsautomat. Simpel:
- CLOSED: alles normal, Requests gehen durch.
- OPEN: Problem erkannt. Requests werden sofort abgelehnt, ohne auf den Service zu warten.
- HALF_OPEN: Testphase. Ein paar Requests gehen durch, um zu prüfen ob der Service wieder da ist.
Der entscheidende Moment ist der Wechsel von CLOSED zu OPEN. Threshold überschritten, Breaker offen: ab da wird jeder Request sofort abgelehnt. Kein Warten. Kein Timeout.
Und so sieht das auf Request-Ebene aus:
Starte das Szenario.
Du opferst einzelne Requests, um das System als Ganzes zu retten.
Die Parameter
Drei Werte entscheiden ob dein Breaker nützlich ist oder ständig false-positives wirft:
Failure Threshold: bei wie vielen Fehlern öffnet sich der Breaker? Zu niedrig und er triggert bei jedem Netzwerk-Hickup. Zu hoch und das System ist schon im Eimer bevor er reagiert. Guter Start: 5.
Reset Timeout: wie lange bleibt er offen bevor er HALF_OPEN testet? Zu kurz und du testest bevor der Service hochfahren konnte. Zu lang und du lehnst unnötig ab. Guter Start: 60s.
Success Threshold: wie viele Erfolge in HALF_OPEN bis er wieder schließt? Zu niedrig und ein zufälliger Erfolg reicht. Zu hoch und du blockierst zu lang. Guter Start: 2.
Ehrlich: es gibt keine Universalwerte. Du musst für deinen Use-Case testen.
Backoff dazupacken
Was passiert wenn der Breaker in HALF_OPEN ist und der Service immer noch flaky? Du willst nicht sofort mit voller Last draufhauen:
async function callPaymentService(userId: string) {
let attempt = 0;
while (attempt < 3) {
try {
return await paymentBreaker.execute(() =>
fetch(`${PAYMENT_SERVICE}/charge/${userId}`)
);
} catch (error) {
attempt++;
if (attempt === 3) throw error;
await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 100));
}
}
}
Die Requests backoff exponentiell. Der Breaker verhindert dass zu viele gleichzeitig probieren. Der Service hat Zeit zu recovern.
Fallbacks
Manchmal reicht “Error werfen” nicht. Wenn dein User gerade im Checkout ist und der Payment-Service kurz weg ist, willst du nicht einfach einen 500er zurückgeben. Die einfachste Strategie: Cache die letzte gute Antwort.
async function getRecommendations(userId: string) {
try {
const result = await recommendationBreaker.execute(() =>
fetch(`${RECOMMENDATION_SERVICE}/recommend/${userId}`)
);
await cache.set(`recs:${userId}`, result);
return result;
} catch {
return await cache.get(`recs:${userId}`) || [];
}
}
Der beste Fallback ist keiner — design deine Systeme so, dass sie ohne einzelne Services funktionieren. Aber die Realität ist selten so sauber.
Monitoring
Ohne Monitoring weißt du nicht mal, dass dein Breaker regelmäßig aufmacht. Drei Signale die du tracken solltest:
- Breaker öffnet sich regelmäßig: dein Downstream-Service hat ein chronisches Problem
- Failure Rate über 5% für mehr als 5 Minuten: Alert
- Breaker bleibt lange OPEN: der Service hat ein tieferes Problem als einen kurzen Ausfall
Wenn dein Breaker dreimal am Tag aufmacht und du davon nichts mitbekommst, hast du keinen Circuit Breaker. Du hast eine Zeitbombe.