Einleitung – Wenn der Domino-Stein fällt
Stell dir vor, unser SAGA Orchestrator (Saga Pattern Teil 3)läuft auf Hochtouren. Kunden bestellen wie verrückt. Plötzlich wird der Inventory Service langsam. Vielleicht liegt es an einer Datenbank-Sperre oder einem schlechten Deployment.
Was passiert ohne Schutzmechanismus?
- Der Orchestrator sendet eine Anfrage an das Inventory.
- Er wartet auf eine Antwort… und wartet… und wartet.
- Während er wartet, ist der Thread blockiert.
- Neue Bestellungen kommen rein, neue Threads werden gestartet, die ebenfalls warten.
- Innerhalb von Sekunden sind alle Threads des Orchestrators aufgebraucht.
- Ergebnis: Der Orchestrator stürzt ab oder wird unerreichbar. Obwohl eigentlich nur das Inventar ein Problem hat, ist jetzt das gesamte Bestellsystem down.
Das nennt man Cascading Failure (Kaskadenartiger Ausfall).
Um das zu verhindern, brauchen wir eine Sicherung. Genau wie in deinem Sicherungskasten zu Hause: Wenn zu viel Strom fließt (oder hier: zu viele Fehler passieren), unterbrechen wir die Leitung sofort, um das Haus (unseren Orchestrator) vor dem Abfackeln zu schützen.
Das Konzept – Die drei Zustände
Das Circuit Breaker Pattern funktioniert als Zustandsautomat (State Machine), der sich zwischen dem Aufrufer (Orchestrator) und dem Ziel (Inventory Service) befindet.
Er kennt drei Zustände:
CLOSED (Geschlossen - Normalbetrieb):
- Der Strom fließt. Anfragen werden ganz normal an den Service weitergeleitet.
- Der Breaker zählt im Hintergrund mit: “War die Anfrage erfolgreich oder ein Fehler/Timeout?”
OPEN (Offen - Die Sicherung ist raus):
- Wenn eine bestimmte Fehlerschwelle überschritten wird (z.B. 50% der Anfragen scheitern), springt der Breaker auf
OPEN. - Fail Fast: Anfragen werden jetzt nicht mehr an den externen Service weitergeleitet. Stattdessen wirft der Breaker sofort eine Exception oder ruft eine Fallback-Methode auf. Das entlastet den kaputten Service und schützt die Ressourcen des Aufrufers.
- Wenn eine bestimmte Fehlerschwelle überschritten wird (z.B. 50% der Anfragen scheitern), springt der Breaker auf
HALF-OPEN (Halb-Offen - Der Testlauf):
- Nach einer definierten Wartezeit erlaubt der Breaker eine kleine Anzahl von “Test-Anfragen” durch.
- Sind diese erfolgreich? Super -> Zurück zu CLOSED.
- Schlagen sie wieder fehl? Pech gehabt -> Zurück zu OPEN (Wartezeit beginnt von vorn).
Die Implementierung – Resilience4j in Action
Genug Theorie. Wie sieht das in unserem Spring Boot Orchestrator aus? Wir nutzen Resilience4j, den aktuellen Industriestandard für Resilience in Java.
Zuerst fügen wir die Dependency hinzu (in pom.xml):
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
Nehmen wir an, unser Orchestrator hat eine Komponente, die den Inventory Service aufruft (z.B. über einen REST-Client oder RabbitMQ-RPC). Wir sichern diese Methode ab:
@Service
public class InventoryClient {
private final RestTemplate restTemplate;
public InventoryClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
// Name der Instanz: "inventory"
@CircuitBreaker(name = "inventory", fallbackMethod = "reserveInventoryFallback")
public boolean reserveInventory(String orderId, String sku) {
// Dieser Aufruf könnte langsam sein oder fehlschlagen
ResponseEntity<String> response = restTemplate.postForEntity(
"http://inventory-service/reserve",
new ReservationRequest(orderId, sku),
String.class
);
return response.getStatusCode().is2xxSuccessful();
}
// FALLBACK METHODE
// Muss die gleiche Signatur haben + Exception Parameter
public boolean reserveInventoryFallback(String orderId, String sku, Throwable t) {
System.out.println("Circuit Breaker OPEN! Inventory Service is down. Reason: " + t.getMessage());
// Was tun wir jetzt?
// Option A: Wir geben 'false' zurück, damit der Orchestrator weiß, es hat nicht geklappt.
// Option B: Wir werfen eine spezifische Exception, die eine Kompensation auslöst.
return false;
}
}
Was passiert hier?
Wenn der inventory-service down ist und der Circuit Breaker auf OPEN springt, wird reserveInventory gar nicht mehr ausgeführt. Stattdessen springt Spring direkt in reserveInventoryFallback. Der Orchestrator bekommt sofort ein false zurück, ohne blockiert zu werden.
Die Konfiguration – Feintuning
Wann genau die Sicherung fliegt, konfigurieren wir in der application.yml. Resilience4j ist hier extrem flexibel.
resilience4j:
circuitbreaker:
instances:
inventory:
registerHealthIndicator: true
slidingWindowSize: 10 # Betrachte die letzten 10 Anfragen
minimumNumberOfCalls: 5 # Mindestens 5 Anfragen nötig zur Berechnung
failureRateThreshold: 50 # Bei 50% Fehlerquote -> OPEN
waitDurationInOpenState: 5s # Warte 5s, bevor du HALF-OPEN versuchst
permittedNumberOfCallsInHalfOpenState: 3 # 3 Testanfragen im HALF-OPEN
automaticTransitionFromOpenToHalfOpenEnabled: true
Das bedeutet: Wenn von den letzten 10 Anfragen 5 oder mehr fehlschlagen, öffnet sich der Breaker. Für 5 Sekunden werden alle Anfragen sofort geblockt. Danach lässt er 3 Testanfragen durch.
Fazit
Das Circuit Breaker Pattern ist wie ein Airbag für deine Microservices. Es verhindert nicht den Unfall (den Ausfall des externen Services), aber es verhindert, dass der Unfall tödlich für dein ganzes System endet.
In Kombination mit dem SAGA Orchestrator sorgt es dafür, dass der Orchestrator auch dann stabil bleibt und Fehler sauber handhaben kann, wenn die umgebenden Services im Chaos versinken.
