Ein Leistungsschalter erkennt Ausfälle und kapselt die Logik zur Behandlung dieser Ausfälle so ein, dass verhindert wird, dass der Fehler ständig erneut auftritt. Sie sind beispielsweise nützlich, wenn Sie Netzwerkaufrufe an externe Dienste, Datenbanken oder eigentlich jeden Teil Ihres Systems verarbeiten, der vorübergehend ausfallen könnte. Durch den Einsatz eines Leistungsschalters können Sie kaskadierende Ausfälle verhindern, vorübergehende Fehler verwalten und bei einem Systemausfall ein stabiles und reaktionsfähiges System aufrechterhalten.
Kaskadierende Ausfälle treten auf, wenn ein Ausfall in einem Teil des Systems Ausfälle in anderen Teilen auslöst, was zu weitreichenden Störungen führt. Ein Beispiel wäre, wenn ein Mikrodienst in einem verteilten System nicht mehr reagiert, was dazu führt, dass abhängige Dienste eine Zeitüberschreitung erleiden und schließlich ausfallen. Abhängig vom Umfang der Anwendung können die Auswirkungen dieser Fehler katastrophal sein, was zu einer Verschlechterung der Leistung und wahrscheinlich sogar zu einer Beeinträchtigung des Benutzererlebnisses führt.
Ein Leistungsschalter selbst ist eine Technik/ein Muster und es gibt drei verschiedene Betriebszustände, über die wir sprechen werden:
2. Offener Zustand: Im offenen Zustand schlägt der Leistungsschalter alle eingehenden Anforderungen sofort fehl, ohne zu versuchen, den Zieldienst zu kontaktieren. Der Zustand wird eingegeben, um eine weitere Überlastung des ausgefallenen Dienstes zu verhindern und ihm Zeit zur Wiederherstellung zu geben. Nach einer vordefinierten Zeitspanne geht der Leistungsschalter in den halboffenen Zustand. Ein nachvollziehbares Beispiel ist dieses; Stellen Sie sich vor, in einem Online-Shop tritt plötzlich ein Problem auf, bei dem jeder Kaufversuch fehlschlägt. Um eine Überlastung des Systems zu vermeiden, nimmt das Geschäft vorübergehend keine neuen Kaufanfragen mehr an.
3. Halboffener Zustand: Im halboffenen Zustand lässt der Leistungsschalter eine (konfigurierbare) begrenzte Anzahl von Testanforderungen an den Zieldienst durch. Und wenn diese Anfragen erfolgreich sind, geht der Schaltkreis wieder in den geschlossenen Zustand über. Wenn sie ausfallen, kehrt der Stromkreis in den offenen Zustand zurück. Im Beispiel des Online-Shops, den ich oben im geöffneten Zustand gegeben habe, beginnt der Online-Shop hier, einige Kaufversuche zuzulassen, um zu sehen, ob das Problem behoben wurde. Wenn diese wenigen Versuche erfolgreich sind, wird das Geschäft seinen Service wieder vollständig öffnen, um neue Kaufanfragen anzunehmen.
Dieses Diagramm zeigt, wenn der Leistungsschalter versucht zu sehen, ob Anforderungen an Dienst B erfolgreich sind, und dann scheitert/bricht er ab:
Das Folgediagramm zeigt dann, wenn die Testanfragen an Dienst B erfolgreich sind, der Stromkreis geschlossen ist und alle weiteren Anrufe wieder an Dienst B weitergeleitet werden:
Hinweis: Zu den wichtigsten Konfigurationen für einen Leistungsschalter gehören der Fehlerschwellenwert (Anzahl der Ausfälle, die zum Öffnen des Stromkreises erforderlich sind), das Timeout für den offenen Zustand und die Anzahl der Testanforderungen im halboffenen Zustand Zustand.
Es ist wichtig zu erwähnen, dass Vorkenntnisse in Go erforderlich sind, um diesem Artikel folgen zu können.
Wie jedes Software-Engineering-Muster können Leistungsschalter in verschiedenen Sprachen implementiert werden. Dieser Artikel konzentriert sich jedoch auf die Implementierung in Golang. Für diesen Zweck stehen zwar mehrere Bibliotheken zur Verfügung, wie z. B. goresilience, go-resiliency und gobreaker, wir werden uns jedoch speziell auf die Verwendung der gobreaker-Bibliothek konzentrieren.
Profi-Tipp: Sie können die interne Implementierung des Gobreaker-Pakets hier sehen.
Betrachten wir eine einfache Golang-Anwendung, in der ein Leistungsschalter implementiert ist, um Aufrufe an eine externe API zu verarbeiten. Dieses einfache Beispiel zeigt, wie ein externer API-Aufruf mit der Circuit-Breaker-Technik umschlossen wird:
Lass uns auf ein paar wichtige Dinge eingehen:
Lassen Sie uns einige Komponententests schreiben, um die Implementierung unseres Leistungsschalters zu überprüfen. Ich werde nur die kritischsten Komponententests erläutern, um sie zu verstehen. Den vollständigen Code finden Sie hier.
t.Run("FailedRequests", func(t *testing.T) { // Override callExternalAPI to simulate failure callExternalAPI = func() (int, error) { return 0, errors.New("simulated failure") } for i := 0; i < 4; i++ { _, err := cb.Execute(func() (interface{}, error) { return callExternalAPI() }) if err == nil { t.Fatalf("expected error, got none") } } if cb.State() != gobreaker.StateOpen { t.Fatalf("expected circuit breaker to be open, got %v", cb.State()) } })
//Simulates the circuit breaker being open, //wait for the defined timeout, //then check if it closes again after a successful request. t.Run("RetryAfterTimeout", func(t *testing.T) { // Simulate circuit breaker opening callExternalAPI = func() (int, error) { return 0, errors.New("simulated failure") } for i := 0; i < 4; i++ { _, err := cb.Execute(func() (interface{}, error) { return callExternalAPI() }) if err == nil { t.Fatalf("expected error, got none") } } if cb.State() != gobreaker.StateOpen { t.Fatalf("expected circuit breaker to be open, got %v", cb.State()) } // Wait for timeout duration time.Sleep(settings.Timeout + 1*time.Second) //We expect that after the timeout period, //the circuit breaker should transition to the half-open state. // Restore original callExternalAPI to simulate success callExternalAPI = func() (int, error) { resp, err := http.Get(server.URL) if err != nil { return 0, err } defer resp.Body.Close() return resp.StatusCode, nil } _, err := cb.Execute(func() (interface{}, error) { return callExternalAPI() }) if err != nil { t.Fatalf("expected no error, got %v", err) } if cb.State() != gobreaker.StateHalfOpen { t.Fatalf("expected circuit breaker to be half-open, got %v", cb.State()) } //After verifying the half-open state, another successful request is simulated to ensure the circuit breaker transitions back to the closed state. for i := 0; i < int(settings.MaxRequests); i++ { _, err = cb.Execute(func() (interface{}, error) { return callExternalAPI() }) if err != nil { t.Fatalf("expected no error, got %v", err) } } if cb.State() != gobreaker.StateClosed { t.Fatalf("expected circuit breaker to be closed, got %v", cb.State()) } })
t.Run("ReadyToTrip", func(t *testing.T) { failures := 0 settings.ReadyToTrip = func(counts gobreaker.Counts) bool { failures = int(counts.ConsecutiveFailures) return counts.ConsecutiveFailures > 2 // Trip after 2 failures } cb = gobreaker.NewCircuitBreaker(settings) // Simulate failures callExternalAPI = func() (int, error) { return 0, errors.New("simulated failure") } for i := 0; i < 3; i++ { _, err := cb.Execute(func() (interface{}, error) { return callExternalAPI() }) if err == nil { t.Fatalf("expected error, got none") } } if failures != 3 { t.Fatalf("expected 3 consecutive failures, got %d", failures) } if cb.State() != gobreaker.StateOpen { t.Fatalf("expected circuit breaker to be open, got %v", cb.State()) } })
Wir können noch einen Schritt weiter gehen, indem wir unserer Leistungsschalterimplementierung eine exponentielle Backoff-Strategie hinzufügen. Wir werden diesen Artikel einfach und prägnant halten, indem wir ein Beispiel der exponentiellen Backoff-Strategie demonstrieren. Es gibt jedoch noch weitere erwähnenswerte fortgeschrittene Strategien für Leistungsschalter, wie z. B. Lastabwurf, Schottung, Fallback-Mechanismen, Kontext und Löschung. Diese Strategien verbessern grundsätzlich die Robustheit und Funktionalität von Leistungsschaltern. Hier ist ein Beispiel für die Verwendung der exponentiellen Backoff-Strategie:
Exponentielles Backoff
Leistungsschalter mit exponentiellem Backoff
Lassen Sie uns ein paar Dinge klarstellen:
Benutzerdefinierte Backoff-Funktion: Die exponentialBackoff-Funktion implementiert eine exponentielle Backoff-Strategie mit einem Jitter. Grundsätzlich wird die Backoff-Zeit basierend auf der Anzahl der Versuche berechnet und sichergestellt, dass die Verzögerung mit jedem Wiederholungsversuch exponentiell zunimmt.
Verarbeitung von Wiederholungsversuchen: Wie Sie im /api-Handler sehen können, enthält die Logik jetzt eine Schleife, die versucht, die externe API bis zu einer bestimmten Anzahl von Versuchen aufzurufen (Versuche := 5). Nach jedem fehlgeschlagenen Versuch warten wir eine durch die exponentialBackoff-Funktion bestimmte Dauer ab, bevor wir es erneut versuchen.
Ausführung des Leistungsschalters: Der Leistungsschalter wird innerhalb der Schleife verwendet. Wenn der externe API-Aufruf erfolgreich ist ( err == nil), wird die Schleife unterbrochen und das erfolgreiche Ergebnis zurückgegeben. Wenn alle Versuche fehlschlagen, wird ein HTTP 503-Fehler (Dienst nicht verfügbar) zurückgegeben.
Die Integration einer benutzerdefinierten Backoff-Strategie in eine Leistungsschalterimplementierung zielt tatsächlich darauf ab, vorübergehende Fehler eleganter zu behandeln. Die zunehmenden Verzögerungen zwischen den Wiederholungsversuchen tragen dazu bei, die Belastung ausgefallener Dienste zu verringern und ihnen Zeit für die Wiederherstellung zu geben. Wie aus unserem obigen Code hervorgeht, wurde unsere exponentialBackoff-Funktion eingeführt, um Verzögerungen zwischen Wiederholungsversuchen beim Aufruf einer externen API hinzuzufügen.
Darüber hinaus können wir Metriken und Protokollierung integrieren, um Zustandsänderungen von Leistungsschaltern zu überwachen, indem wir Tools wie Prometheus für die Echtzeitüberwachung und -warnung nutzen. Hier ist ein einfaches Beispiel:
Implementierung eines Leistungsschaltermusters mit erweiterten Strategien in go
Wie Sie sehen werden, haben wir jetzt Folgendes getan:
Profi-Tipp: Die Init-Funktion in Go wird verwendet, um den Status eines Pakets zu initialisieren, bevor die Hauptfunktion oder ein anderer Code im Paket ausgeführt wird. In diesem Fall registriert die Init-Funktion die requestCount-Metrik bei Prometheus. Und dies stellt im Wesentlichen sicher, dass Prometheus diese Metrik kennt und mit der Datenerfassung beginnen kann, sobald die Anwendung ausgeführt wird.
Wir erstellen den Leistungsschalter mit benutzerdefinierten Einstellungen, einschließlich der ReadyToTrip-Funktion, die den Fehlerzähler erhöht und bestimmt, wann der Stromkreis ausgelöst werden soll.
OnStateChange, um Zustandsänderungen zu protokollieren und die entsprechende Prometheus-Metrik zu erhöhen
Wir stellen die Prometheus-Metriken am /metrics-Endpunkt zur Verfügung
Zum Abschluss dieses Artikels hoffe ich, dass Sie gesehen haben, wie Leistungsschalter eine große Rolle beim Aufbau belastbarer und zuverlässiger Systeme spielen. Indem sie kaskadierende Ausfälle proaktiv verhindern, stärken sie die Zuverlässigkeit von Microservices und verteilten Systemen und sorgen so für ein nahtloses Benutzererlebnis auch in schwierigen Situationen.
Denken Sie daran, dass jedes System, das auf Skalierbarkeit ausgelegt ist, Strategien zur reibungslosen Bewältigung von Ausfällen und zur schnellen Wiederherstellung beinhalten muss - Oluwafemi, 2024
Ursprünglich veröffentlicht unter https://oluwafemiakinde.dev am 7. Juni 2024.
Das obige ist der detaillierte Inhalt vonLeistungsschalter in Go: Stoppen Sie kaskadierende Ausfälle. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!