Kapitel 1: Einführung in das Wiederholen in Go

1.1 Verständnis der Notwendigkeit von Wiederholungsmechanismen

In vielen Rechenszenarien, insbesondere bei der Arbeit mit verteilten Systemen oder der Netzwerkkommunikation, können Operationen aufgrund transienter Fehler fehlschlagen. Diese Fehler sind oft vorübergehende Probleme wie Netzwerkinstabilität, kurzzeitige Nichtverfügbarkeit eines Dienstes oder Zeitüberschreitungen. Anstatt sofort zu scheitern, sollten Systeme so konzipiert sein, dass sie Operationen, die auf solche vorübergehenden Fehler stoßen, wiederholen. Dieser Ansatz verbessert die Zuverlässigkeit und Robustheit.

Wiederholungsmechanismen können in Anwendungen, in denen Konsistenz und Vollständigkeit von Operationen erforderlich sind, entscheidend sein. Sie können auch die Fehlerrate reduzieren, die Endbenutzer erleben. Die Implementierung eines Wiederholungsmechanismus bringt jedoch Herausforderungen mit sich, wie z.B. die Entscheidung, wie oft und wie lange vor dem Aufgeben wiederholt werden soll. Genau hier spielen Backoff-Strategien eine bedeutende Rolle.

1.2 Überblick über die go-retry-Bibliothek

Die go-retry-Bibliothek in Go bietet eine flexible Möglichkeit, Ihrem Anwendungen mit verschiedenen Backoff-Strategien Wiederholungslogik hinzuzufügen. Die wichtigsten Funktionen sind:

  • Erweiterbarkeit: Wie das http-Paket von Go ist auch go-retry darauf ausgelegt, mit Zwischensoftware erweiterbar zu sein. Sie können sogar Ihre eigenen Backoff-Funktionen schreiben oder die praktischen Filter verwenden.
  • Unabhängigkeit: Die Bibliothek verlässt sich nur auf die Go-Standardbibliothek und vermeidet externe Abhängigkeiten, wodurch Ihr Projekt schlank bleibt.
  • Nebenläufigkeit: Sie ist für den gleichzeitigen Einsatz sicher und kann mit Goroutinen ohne zusätzliche Probleme arbeiten.
  • Kontextbewusst: Sie unterstützt native Go-Kontexte für Timeout und Abbruch und integriert sich nahtlos in das Nebenläufigkeitsmodell von Go.

Kapitel 2: Importieren von Bibliotheken

Bevor Sie die go-retry-Bibliothek verwenden können, muss sie in Ihr Projekt importiert werden. Dies kann mit go get erfolgen, dem Go-Befehl zum Hinzufügen von Abhängigkeiten zu Ihrem Modul. Öffnen Sie einfach Ihr Terminal und führen Sie folgendes aus:

go get github.com/sethvargo/go-retry

Dieser Befehl wird die go-retry-Bibliothek abrufen und zu den Abhängigkeiten Ihres Projekts hinzufügen. Danach können Sie sie wie jedes andere Go-Paket in Ihren Code importieren.

Kapitel 3: Implementierung einer einfachen Wiederholungslogik

3.1 Einfache Wiederholung mit konstantem Backoff

Die einfachste Form der Wiederholungslogik beinhaltet das Warten auf eine konstante Dauer zwischen jedem Wiederholungsversuch. Sie können go-retry verwenden, um Wiederholungen mit konstantem Backoff durchzuführen.

Hier ist ein Beispiel, wie man mit go-retry einen konstanten Backoff verwendet:

package main

import (
  "context"
  "time"
  "github.com/sethvargo/go-retry"
)

func main() {
    ctx := context.Background()
    
    // Erstellen eines neuen konstanten Backoffs
    backoff := retry.NewConstant(1 * time.Second)

    // Verpacken Sie Ihre Wiederholungslogik in eine Funktion, die an retry.Do übergeben wird
    operation := func(ctx context.Context) error {
        // Ihr Code hier. Geben Sie retry.RetryableError(err) zurück, um zu wiederholen, oder nil, um anzuhalten.
        // Beispiel:
        // err := someOperation()
        // if err != nil {
        //   return retry.RetryableError(err)
        // }
        // return nil

        return nil
    }
    
    // Verwenden von retry.Do mit dem gewünschten Kontext, der Backoff-Strategie und der Operation
    if err := retry.Do(ctx, backoff, operation); err != nil {
        // Fehler behandeln
    }
}

In diesem Beispiel wird die Funktion retry.Do den operation-Funktion alle 1 Sekunde weiterhin versuchen, bis sie erfolgreich ist oder der Kontext abläuft oder abgebrochen wird.

3.2 Implementierung des exponentiellen Backoff

Exponentieller Backoff erhöht die Wartezeit zwischen den Wiederholungen exponentiell. Diese Strategie hilft, die Belastung des Systems zu reduzieren und ist besonders nützlich bei der Arbeit mit großangelegten Systemen oder Cloud-Diensten.

So wird der exponentielle Backoff mit go-retry verwendet:

package main

import (
  "context"
  "time"
  "github.com/sethvargo/go-retry"
)

func main() {
    ctx := context.Background()

    // Erstellen eines neuen exponentiellen Backoffs
    backoff := retry.NewExponential(1 * time.Second)

    // Bereitstellung Ihrer wiederholbaren Operation
    operation := func(ctx context.Context) error {
        // Implementieren Sie die Operation wie zuvor gezeigt
        return nil
    }
    
    // Verwendung von retry.Do zum Ausführen der Operation mit exponentiellem Backoff
    if err := retry.Do(ctx, backoff, operation); err != nil {
        // Fehler behandeln
    }
}

Im Falle des exponentiellen Backoffs erfolgen die Wiederholungen bei einer anfänglichen Wartezeit von 1 Sekunde, dann nach 1s, 2s, 4s usw., wobei die Wartezeit zwischen den folgenden Wiederholungen exponentiell zunimmt.

3.3 Fibonacci-Backoff-Strategie

Die Fibonacci-Backoff-Strategie verwendet die Fibonacci-Sequenz, um die Wartezeit zwischen Wiederholungsversuchen zu bestimmen, was eine gute Strategie für netzwerkbezogene Probleme ist, bei denen eine allmählich ansteigende Time-Out-Dauer vorteilhaft ist.

Die Implementierung der Fibonacci-Backoff-Strategie mit go-retry wird unten gezeigt:

package main

import (
  "context"
  "time"
  "github.com/sethvargo/go-retry"
)

func main() {
    ctx := context.Background()

    // Erstellen eines neuen Fibonacci-Backoffs
    backoff := retry.NewFibonacci(1 * time.Second)

    // Definition einer Operation zum Wiederholen
    operation := func(ctx context.Context) error {
        // Hier würde die Logik stehen, um die Aktion auszuführen, die möglicherweise fehlschlägt und wiederholt werden muss
        return nil
    }
    
    // Ausführen der Operation mit Fibonacci-Backoff unter Verwendung von retry.Do
    if err := retry.Do(ctx, backoff, operation); err != nil {
        // Fehlerbehandlung
    }
}

Bei einem Fibonacci-Backoff mit einem Anfangswert von 1 Sekunde finden die Wiederholungsversuche nach 1s, 1s, 2s, 3s, 5s usw. gemäß der Fibonacci-Sequenz statt.

Kapitel 4: Fortgeschrittene Wiederholungstechniken und Middleware

4.1 Verwendung von Jitter bei Wiederholungen

Bei der Implementierung von Wiederholungslogik ist es wichtig, die Auswirkungen gleichzeitiger Wiederholungen auf ein System zu berücksichtigen, die zu einem "Rudelschlag"-Problem führen können. Zur Minderung dieses Problems können wir zufälligen Jitter zu den Backoff-Intervallen hinzufügen. Diese Technik hilft dabei, die Wiederholungsversuche zu staffeln und die Wahrscheinlichkeit von gleichzeitigen Wiederholungen mehrerer Clients zu reduzieren.

Beispiel für das Hinzufügen von Jitter:

b := retry.NewFibonacci(1 * time.Second)

// Den nächsten Wert zurückgeben, +/- 500ms
b = retry.WithJitter(500 * time.Millisecond, b)

// Den nächsten Wert zurückgeben, +/- 5% des Ergebnisses
b = retry.WithJitterPercent(5, b)

4.2 Festlegen von maximalen Wiederholungen

In einigen Szenarien ist es notwendig, die Anzahl der Wiederholungsversuche zu begrenzen, um anhaltende und ineffektive Wiederholungen zu vermeiden. Durch Festlegung der maximalen Anzahl von Wiederholungen können wir die Anzahl der Versuche steuern, bevor die Operation aufgegeben wird.

Beispiel für die Festlegung maximaler Wiederholungen:

b := retry.NewFibonacci(1 * time.Second)

// Nach 4 Wiederholungen stoppen, wenn der 5. Versuch fehlgeschlagen ist
b = retry.WithMaxRetries(4, b)

4.3 Begrenzung einzelner Backoff-Dauern

Um sicherzustellen, dass einzelne Backoff-Dauern einen bestimmten Schwellenwert nicht überschreiten, können wir das CappedDuration-Middleware verwenden. Dies verhindert, dass übermäßig lange Backoff-Intervalle berechnet werden und verleiht dem Wiederholungsverhalten Vorhersehbarkeit.

Beispiel für das Begrenzen einzelner Backoff-Dauern:

b := retry.NewFibonacci(1 * time.Second)

// Sicherstellen, dass der maximale Wert 2s beträgt
b = retry.WithCappedDuration(2 * time.Second, b)

4.4 Steuern der gesamten Wiederholungsdauer

In Szenarien, in denen eine Begrenzung der Gesamtdauer für den gesamten Wiederholungsprozess erforderlich ist, kann das WithMaxDuration-Middleware verwendet werden, um eine maximale Gesamtausführungszeit anzugeben. Dies stellt sicher, dass der Wiederholungsprozess nicht endlos fortgesetzt wird und setzt ein Zeitbudget für die Wiederholungen.

Beispiel für die Steuerung der gesamten Wiederholungsdauer:

b := retry.NewFibonacci(1 * time.Second)

// Sicherstellen, dass die maximale Gesamtwiederholungszeit 5s beträgt
b = retry.WithMaxDuration(5 * time.Second, b)