Einführung

In der modernen Softwareentwicklung ist die Zuverlässigkeit von Anwendungen von entscheidender Bedeutung. Mikroservices-Architekturen und verteilte Systeme sind sehr populär geworden, was jedoch auch Herausforderungen mit sich bringt, insbesondere bei der Handhabung von Fehlern und der Resilienz von Anwendungen. Eine der Techniken zur Verbesserung der Resilienz ist das Circuit Breaker Muster. Resilience4j ist eine leichtgewichtige, auf Funktionen basierende Java-Bibliothek, die verschiedene Resilienz-Muster unterstützt, darunter auch Circuit Breaker. In diesem Artikel werden wir detailliert erläutern, wie man mit Resilience4j einen Circuit Breaker in Java implementiert und die verschiedenen Konfigurationsoptionen erklärt.

Was ist ein Circuit Breaker?

Ein Circuit Breaker ist ein Designmuster, das dazu dient, den Ausfall eines Services zu verhindern, indem Anfragen an einen fehlerhaften Service unterbrochen werden. Es arbeitet ähnlich wie ein elektrischer Sicherungsautomat und hat drei Zustände:

  1. Closed: Der Circuit Breaker leitet die Anfragen weiter.
  2. Open: Der Circuit Breaker blockiert alle Anfragen und lässt keine weiteren Versuche zu, bis eine festgelegte Wartezeit abgelaufen ist.
  3. Half-Open: Der Circuit Breaker lässt eine begrenzte Anzahl von Anfragen durch, um zu testen, ob der Service wieder funktionsfähig ist.

Einrichtung von Resilience4j

Bevor wir mit der Implementierung beginnen, müssen wir die notwendigen Abhängigkeiten zu unserem Projekt hinzufügen. Für ein Maven-Projekt fügen wir folgende Abhängigkeit in der pom.xml hinzu:

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-circuitbreaker</artifactId>
    <version>1.7.1</version>
</dependency>Code-Sprache: HTML, XML (xml)

Für Gradle-Projekte fügen wir folgende Zeile in der build.gradle Datei hinzu:

implementation 'io.github.resilience4j:resilience4j-circuitbreaker:1.7.1'Code-Sprache: JavaScript (javascript)

Grundlegende Implementierung

Nun, da die Abhängigkeiten hinzugefügt sind, können wir mit der Implementierung eines einfachen Circuit Breakers beginnen. Zuerst müssen wir eine Instanz des Circuit Breakers erstellen und konfigurieren.

import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;

import java.time.Duration;

public class CircuitBreakerExample {
    public static void main(String[] args) {
        // Konfiguration des Circuit Breakers
        CircuitBreakerConfig config = CircuitBreakerConfig.custom()
                .failureRateThreshold(50) // 50% Fehlerrate
                .waitDurationInOpenState(Duration.ofSeconds(10)) // 10 Sekunden im Open-Zustand
                .slidingWindowSize(20) // Größe des Sliding Window
                .build();

        // Registrierung des Circuit Breakers
        CircuitBreakerRegistry registry = CircuitBreakerRegistry.of(config);

        // Erstellen des Circuit Breakers
        CircuitBreaker circuitBreaker = registry.circuitBreaker("exampleCircuitBreaker");

        // Anwendung des Circuit Breakers
        Supplier<String> decoratedSupplier = CircuitBreaker
                .decorateSupplier(circuitBreaker, CircuitBreakerExample::remoteServiceCall);

        // Ausführen des dekorierten Suppliers
        String result = Try.ofSupplier(decoratedSupplier)
                .recover(throwable -> "Fallback").get();

        System.out.println(result);
    }

    // Beispiel für einen Remote Service Call, der fehlschlägt
    public static String remoteServiceCall() {
        throw new RuntimeException("Service not available");
    }
}Code-Sprache: JavaScript (javascript)

In diesem Beispiel konfigurieren wir einen Circuit Breaker mit einer Fehlerrate von 50%, einer Wartezeit von 10 Sekunden im offenen Zustand und einem Sliding Window von 20 Anfragen. Wir dekorieren dann einen Supplier, der einen Remote Service Call simuliert. Wenn der Remote Service Call fehlschlägt, greift der Circuit Breaker ein und der Fallback-Mechanismus liefert einen Standardwert zurück.

Erweiterte Konfigurationsoptionen

Die grundlegende Implementierung kann durch verschiedene Konfigurationsoptionen erweitert werden, um spezifischeren Anforderungen gerecht zu werden. Hier sind einige erweiterte Konfigurationsoptionen:

Fehlerrate (failureRateThreshold)

Die Fehlerrate definiert den Prozentsatz der fehlgeschlagenen Anfragen, bei dem der Circuit Breaker in den Open-Zustand wechselt. In unserem Beispiel bedeutet eine Fehlerrate von 50%, dass der Circuit Breaker in den Open-Zustand wechselt, wenn mindestens die Hälfte der Anfragen innerhalb des Sliding Windows fehlschlagen.

.failureRateThreshold(50) // 50% FehlerrateCode-Sprache: JavaScript (javascript)

Wartezeit im offenen Zustand (waitDurationInOpenState)

Die Wartezeit definiert, wie lange der Circuit Breaker im Open-Zustand bleibt, bevor er in den Half-Open-Zustand wechselt. In unserem Beispiel beträgt diese Wartezeit 10 Sekunden.

.waitDurationInOpenState(Duration.ofSeconds(10)) // 10 Sekunden im Open-ZustandCode-Sprache: JavaScript (javascript)

Sliding Window (slidingWindowSize)

Das Sliding Window definiert die Anzahl der Anfragen, die zur Berechnung der Fehlerrate herangezogen werden. In unserem Beispiel umfasst das Sliding Window 20 Anfragen. Es gibt zwei Modi für das Sliding Window:

  1. Count-based sliding window: Das Fenster basiert auf einer festen Anzahl von Anfragen.
  2. Time-based sliding window: Das Fenster basiert auf einem festen Zeitintervall.

In unserem Beispiel verwenden wir ein Count-based sliding window.

.slidingWindowSize(20) // Größe des Sliding WindowCode-Sprache: JavaScript (javascript)

Ausnahmen (recordExceptions und ignoreExceptions)

Wir können spezifische Ausnahmen definieren, die vom Circuit Breaker als Fehler gezählt oder ignoriert werden sollen.

.recordExceptions(RuntimeException.class, TimeoutException.class) // Ausnahmen, die gezählt werden
.ignoreExceptions(IgnoredException.class) // Ausnahmen, die ignoriert werdenCode-Sprache: JavaScript (javascript)

Ereignisüberwachung

Resilience4j bietet auch die Möglichkeit, Ereignisse zu überwachen und zu protokollieren, die vom Circuit Breaker ausgelöst werden. Dies ist nützlich, um Einblicke in das Verhalten des Circuit Breakers zu erhalten.

circuitBreaker.getEventPublisher()
        .onSuccess(event -> System.out.println("Call succeeded"))
        .onError(event -> System.out.println("Call failed"))
        .onStateTransition(event -> System.out.println("State transitioned to " + event.getStateTransition()));Code-Sprache: CSS (css)

Integration mit Spring Boot

Resilience4j lässt sich auch nahtlos in Spring Boot Anwendungen integrieren. Hier ist ein einfaches Beispiel:

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class MyService {
    private final RestTemplate restTemplate;

    public MyService(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @CircuitBreaker(name = "myServiceCircuitBreaker", fallbackMethod = "fallback")
    public String callExternalService() {
        return restTemplate.getForObject("http://example.com/api", String.class);
    }

    public String fallback(Throwable throwable) {
        return "Fallback response";
    }
}Code-Sprache: JavaScript (javascript)

In diesem Beispiel wird ein REST-Aufruf mit einem Circuit Breaker geschützt. Wenn der Aufruf fehlschlägt, wird die fallback Methode aufgerufen.

Fazit

Die Resilience4j-Bibliothek bietet eine leistungsfähige und flexible Möglichkeit, das Circuit Breaker Muster in Java-Anwendungen zu implementieren. Durch die Verwendung von Resilience4j können Entwickler die Resilienz ihrer Anwendungen erheblich verbessern und die Auswirkungen von Fehlern und Ausfällen minimieren. Mit einer Vielzahl von Konfigurationsmöglichkeiten und der einfachen Integration in Spring Boot ist Resilience4j eine ausgezeichnete Wahl für die Implementierung von Resilienzmustern in modernen Java-Anwendungen.