Nginx-Lastverteilungsstrategien für hohe Verfügbarkeit

Erfahren Sie, wie Sie mit Nginx-Lastverteilung hohe Verfügbarkeit für Ihre Webanwendungen erreichen. Dieser Leitfaden untersucht wesentliche Nginx-Lastverteilungsstrategien, einschließlich Round Robin, gewichteter Round Robin, Least-Connected und IP-Hash. Entdecken Sie praktische Konfigurationsbeispiele, verstehen Sie Health-Check-Mechanismen und implementieren Sie Best Practices, um sicherzustellen, dass Ihre Anwendungen auch bei unterschiedlichen Verkehrslasten zugänglich und leistungsfähig bleiben.

Nginx-Lastverteilungsstrategien für hohe Verfügbarkeit

Nginx-Lastverteilung wird normalerweise nach der ersten schmerzhaften Grenze eingeführt: Ein Anwendungsserver ist zu ausgelastet, benötigt Wartung oder fällt auf eine Weise aus, die die gesamte Website mitreißt. Wenn Sie Nginx vor mehrere Backends setzen, haben Sie Spielraum, um Anfragen zu verteilen, einen Server zu entlasten und gewöhnliche Ausfälle zu überleben.

Es ist an sich keine magische hohe Verfügbarkeit. Open-Source-Nginx kann nach Verbindungsfehlern aufhören, Traffic an ein Backend zu senden, versteht aber nicht tiefgehend, ob Ihre Checkout-Seite, API-Abhängigkeit oder Datenbankverbindung gesund ist. Ein gutes Setup kombiniert Nginx-Upstream-Konfiguration, sinnvolle Timeouts, anwendungsspezifische Health-Endpunkte, externes Monitoring und einen Bereitstellungsprozess, der ein fehlerhaftes Backend schnell entfernen kann.

Lastverteilung verstehen

Im Kern geht es bei der Lastverteilung darum, Client-Anfragen intelligent an einen Pool von Servern zu leiten. Anstatt dass ein einzelner Server den gesamten Traffic verarbeitet, arbeiten mehrere Server zusammen. Dies bietet mehrere wesentliche Vorteile:

  • Hohe Verfügbarkeit: Wenn ein Server auf erkennbare Weise ausfällt, können andere weiterhin Anfragen bearbeiten.
  • Skalierbarkeit: Wenn der Traffic zunimmt, können Sie dem Pool weitere Server hinzufügen, um die Last zu bewältigen.
  • Leistung: Die Verteilung des Traffics verhindert, dass ein einzelner Server überlastet wird, was zu schnelleren Antwortzeiten führt.
  • Zuverlässigkeit: Durch die Beseitigung einzelner Fehlerquellen wird Ihre Anwendung robuster.

Nginx fungiert in einem Lastverteilungs-Setup als Reverse-Proxy. Es empfängt eingehende Client-Anfragen und leitet sie basierend auf einem konfigurierten Algorithmus an einen der verfügbaren Backend-Server weiter. Es empfängt auch die Antwort vom Backend-Server und sendet sie an den Client zurück, wodurch der Prozess für den Endbenutzer transparent wird.

Nginx-Lastverteilungsdirektiven

Nginx verwendet spezifische Direktiven in seiner Konfigurationsdatei (normalerweise nginx.conf oder daraus eingebundene Dateien), um Upstream-Servergruppen und ihr Lastverteilungsverhalten zu definieren.

Der upstream-Block

Der upstream-Block wird verwendet, um eine Gruppe von Servern zu definieren, über die Nginx den Traffic verteilt. Dieser Block wird normalerweise im http-Kontext platziert.

http {
    upstream my_backend_servers {
        # Serverkonfigurationen hier
    }

    server {
        listen 80;
        server_name example.com;

        location / {
            proxy_pass http://my_backend_servers;
        }
    }
}

Innerhalb des upstream-Blocks listen Sie die Backend-Server mit der server-Direktive auf und geben deren IP-Adressen oder Hostnamen und Ports an.

upstream my_backend_servers {
    server backend1.example.com;
    server backend2.example.com;
    server 192.168.1.100:8080;
}

Die proxy_pass-Direktive

Die proxy_pass-Direktive, die innerhalb eines location-Blocks verwendet wird, verweist auf die von Ihnen definierte upstream-Gruppe. Nginx verwendet dann den konfigurierten Lastverteilungsalgorithmus, um für jede Anfrage einen Server aus dieser Gruppe auszuwählen.

Nginx-Lastverteilungsalgorithmen

Nginx unterstützt mehrere Lastverteilungsalgorithmen, jeder mit seinem eigenen Ansatz zur Verteilung des Traffics. Der Standardalgorithmus ist Round Robin.

1. Round Robin (Standard)

Bei Round Robin verteilt Nginx Anfragen sequentiell an jeden Server in der upstream-Gruppe. Jeder Server erhält im Laufe der Zeit einen gleichen Anteil der Last. Es ist einfach, effektiv für identische Server und die am häufigsten verwendete Methode.

Konfiguration:

upstream my_backend_servers {
    server backend1.example.com;
    server backend2.example.com;
    server backend3.example.com;
}

Vorteile:

  • Einfach zu implementieren und zu verstehen.
  • Verteilt die Last gleichmäßig, wenn die Server ähnliche Kapazitäten haben.

Nachteile:

  • Berücksichtigt weder Serverlast noch Antwortzeiten. Ein langsamer Server könnte weiterhin Anfragen erhalten.

2. Gewichteter Round Robin

Gewichteter Round Robin ermöglicht es Ihnen, jedem Server ein Gewicht zuzuweisen. Server mit einem höheren Gewicht erhalten einen proportional größeren Anteil des Traffics. Dies ist nützlich, wenn Sie Server mit unterschiedlichen Kapazitäten haben (z. B. leistungsstärkere Hardware).

Konfiguration:

upstream my_backend_servers {
    server backend1.example.com weight=3;
    server backend2.example.com weight=1;
}

In diesem Beispiel erhält backend1.example.com dreimal mehr Anfragen als backend2.example.com.

Vorteile:

  • Ermöglicht die Lastverteilung basierend auf der Serverkapazität.

Nachteile:

  • Berücksichtigt immer noch nicht die Echtzeit-Serverlast.

3. Least-Connected

Der Least-Connected-Algorithmus leitet Anfragen an den Server mit den wenigsten aktiven Verbindungen. Diese Methode ist dynamischer, da sie die aktuelle Last auf jedem Server berücksichtigt.

Konfiguration:

Um Least-Connected zu aktivieren, fügen Sie einfach den Parameter least_conn zum upstream-Block hinzu:

upstream my_backend_servers {
    least_conn;
    server backend1.example.com;
    server backend2.example.com;
    server backend3.example.com;
}

Vorteile:

  • Verteilt die Last intelligenter, indem die aktuelle Serverlast berücksichtigt wird.
  • Gut für Anwendungen mit unterschiedlichen Verbindungsdauern.

Nachteile:

  • Kann etwas komplexer zu verwalten sein, wenn die Verbindungszahlen schnell schwanken.

4. IP-Hash

Mit IP-Hash bestimmt Nginx, welcher Server eine Anfrage basierend auf einem Hash der Client-IP-Adresse bearbeiten soll. Dies stellt sicher, dass Anfragen von derselben Client-IP-Adresse konsistent an denselben Backend-Server gesendet werden. Dies ist entscheidend für Anwendungen, die auf Sitzungspersistenz (Sticky Sessions) angewiesen sind, ohne gemeinsamen Sitzungsspeicher zu verwenden.

Konfiguration:

Fügen Sie den Parameter ip_hash zum upstream-Block hinzu:

upstream my_backend_servers {
    ip_hash;
    server backend1.example.com;
    server backend2.example.com;
}

Vorteile:

  • Bietet sofortige Sitzungspersistenz.

Nachteile:

  • Kann zu ungleichmäßiger Lastverteilung führen, wenn viele Clients eine einzelne IP-Adresse teilen (z. B. hinter einem NAT-Gateway).
  • Wenn ein Server ausfällt, sind alle Clients, die auf diesen Server gehasht wurden, betroffen, bis der Server wieder online ist oder der Hash neu berechnet wird (obwohl Nginx versucht, umzuleiten).

5. Generischer Hash

Ähnlich wie IP-Hash ermöglicht der generische Hash die Angabe eines Schlüssels für das Hashing. Dieser Schlüssel kann eine Variable wie $request_id, $cookie_jsessionid oder eine Kombination von Variablen sein. Dies bietet mehr Flexibilität für Sitzungspersistenz oder Routing basierend auf bestimmten Anfrageattributen.

Konfiguration:

upstream my_backend_servers {
    hash $remote_addr consistent;
    server backend1.example.com;
    server backend2.example.com;
}

Die Verwendung von consistent mit hash implementiert konsistentes Hashing, das die Neuverteilung von Schlüsseln minimiert, wenn sich die Servergruppe ändert.

Vorteile:

  • Hochflexibel für benutzerdefinierte Routing-Logik.
  • Unterstützt konsistentes Hashing für bessere Stabilität bei Serveränderungen.

Nachteile:

  • Erfordert eine sorgfältige Auswahl des Hashing-Schlüssels.

Health Checks und Serverstatus

Für eine sinnvolle hohe Verfügbarkeit muss Nginx Backends vermeiden, die ausfallen. Die Open-Source-Version macht dies hauptsächlich passiv: Sie bemerkt fehlgeschlagene Versuche beim Proxying von echtem Traffic. Dies hilft bei toten Hosts, verweigerten Verbindungen und einigen Timeout-Fällen. Es ist nicht dasselbe wie ein aktiver Health Check, der alle paar Sekunden /healthz aufruft, bevor Benutzer den Dienst treffen.

max_fails und fail_timeout

Diese Parameter, die zur server-Direktive innerhalb eines upstream-Blocks hinzugefügt werden, steuern, wie Nginx mit fehlgeschlagenen Servern umgeht.

  • max_fails: Die Anzahl der erfolglosen Versuche, innerhalb eines bestimmten fail_timeout-Zeitraums mit einem Server zu kommunizieren. Nach max_fails Fehlern wird der Server als nicht verfügbar markiert.
  • fail_timeout: Die Dauer, für die ein Server als nicht verfügbar betrachtet wird. Nach diesem Zeitraum versucht Nginx erneut, seinen Status zu überprüfen.

Konfiguration:

upstream my_backend_servers {
    server backend1.example.com max_fails=3 fail_timeout=30s;
    server backend2.example.com max_fails=3 fail_timeout=30s;
}

In diesem Beispiel vermeidet Nginx backend1.example.com vorübergehend, wenn es während des Fehlerfensters drei erfolglose Versuche gab. Nach dem Timeout kann Nginx es erneut versuchen. Die Fehler basieren auf Verbindungs-/Proxy-Versuchen, nicht auf einer benutzerdefinierten Anwendungs-Gesundheitsantwort, es sei denn, Sie verwenden zusätzliche Tools oder Nginx Plus-Funktionen.

backup-Parameter

Der backup-Parameter kennzeichnet einen Server als Backup. Er erhält nur dann Traffic, wenn alle anderen aktiven Server in der upstream-Gruppe nicht verfügbar sind.

Konfiguration:

upstream my_backend_servers {
    server backend1.example.com;
    server backend2.example.com;
    server backup.example.com backup;
}

Wenn backend1 und backend2 ausgefallen sind, übernimmt backup.example.com.

Nginx Plus Health Checks

Nginx Plus, die kommerzielle Version, enthält integrierte aktive Health Checks. Es kann regelmäßig Anfragen an Backends senden, Antworten auswerten und nicht gesunde Server entfernen, bevor Benutzerverkehr dorthin geleitet wird. Wenn Sie Open-Source-Nginx verwenden, können Sie dennoch ein solides System aufbauen, aber normalerweise kombinieren Sie es mit externem Monitoring, Service Discovery oder Automatisierung, die Upstream-Ziele bearbeitet/entfernt.

Praktische Konfigurationsbeispiele

Lassen Sie uns diese Konzepte mit häufigen Szenarien in die Praxis umsetzen.

Szenario 1: Einfache Round-Robin-Lastverteilung

Verteilen Sie den Traffic auf zwei identische Webserver.

Konfiguration:

http {
    upstream web_servers {
        server 10.0.0.10;
        server 10.0.0.11;
    }

    server {
        listen 80;
        server_name yourdomain.com;

        location / {
            proxy_pass http://web_servers;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}

Erklärung:

  • upstream web_servers: Definiert eine Gruppe namens web_servers.
  • server 10.0.0.10; und server 10.0.0.11;: Gibt die Backend-Server an.
  • proxy_pass http://web_servers;: Leitet den Traffic an die web_servers-Upstream-Gruppe.
  • proxy_set_header: Diese Direktiven sind entscheidend, um die ursprünglichen Client-Informationen an die Backend-Server weiterzuleiten, was oft für das Logging oder die Anwendungslogik erforderlich ist.

Szenario 2: Lastverteilung mit Sitzungspersistenz (IP-Hash)

Stellen Sie sicher, dass Benutzer mit demselben Backend-Server verbunden bleiben, nützlich für Anwendungen, die Sitzungsdaten lokal speichern.

Verwenden Sie dies nur, wenn Sie den Kompromiss verstehen. Wenn viele Benutzer über dasselbe Büro-NAT, Mobilfunk-Gateway oder Unternehmens-Proxy kommen, kann IP-Hash zu viel Traffic an ein Backend senden. Gemeinsamer Sitzungsspeicher, signierte zustandslose Cookies oder anwendungsspezifische Sitzungsreplikation sind oft sauberer als die Abhängigkeit von der Client-IP-Klebrigkeit.

Konfiguration:

http {
    upstream app_servers {
        ip_hash;
        server 192.168.1.50:8000;
        server 192.168.1.51:8000;
    }

    server {
        listen 80;
        server_name api.yourdomain.com;

        location / {
            proxy_pass http://app_servers;
            # ... andere proxy_set_header-Direktiven ...
        }
    }
}

Szenario 3: Gewichtete Lastverteilung mit Failover

Leiten Sie mehr Traffic an einen leistungsstärkeren Server und halten Sie ein Backup bereit.

Konfiguration:

http {
    upstream balanced_app {
        server app_server_1.local weight=5;
        server app_server_2.local weight=2;
        server app_server_3.local backup;
    }

    server {
        listen 80;
        server_name staging.yourdomain.com;

        location / {
            proxy_pass http://balanced_app;
            # ... andere proxy_set_header-Direktiven ...
        }
    }
}

Hier erhält app_server_1.local 5 Teile des Traffics, app_server_2.local 2 Teile und app_server_3.local bedient nur Anfragen, wenn die anderen beiden nicht verfügbar sind.

Best Practices und Tipps

  • Verwenden Sie proxy_set_header: Setzen Sie immer Header wie Host, X-Real-IP, X-Forwarded-For und X-Forwarded-Proto, damit Ihre Backend-Anwendungen die Details des ursprünglichen Clients kennen.
  • Halten Sie Nginx aktuell: Stellen Sie sicher, dass Sie eine stabile, aktuelle Version von Nginx ausführen, um Sicherheits- und Leistungsverbesserungen zu erhalten.
  • Überwachen Sie Backend-Server: Implementieren Sie zusätzlich zu den internen Health Checks von Nginx externe Überwachungstools. Nginx weiß nur, ob es einen Server erreichen kann, nicht unbedingt, ob die Anwendung auf dem Server ordnungsgemäß funktioniert.
  • Erwägen Sie Nginx Plus: Für geschäftskritische Anwendungen bietet Nginx Plus erweiterte Funktionen wie aktive Health Checks, Sitzungspersistenz und Live-Aktivitätsüberwachung, die die Verwaltung vereinfachen und die Ausfallsicherheit verbessern können.
  • DNS-Lastverteilung: Für die Verkehrsverteilung über Regionen oder mehrere Nginx-Einstiegspunkte kann DNS helfen, aber DNS-Failover hängt vom Resolver-Verhalten und TTLs ab. Behandeln Sie es nicht als sofortiges Failover.
  • SSL-Terminierung: Sie können SSL oft am Load Balancer (Nginx) terminieren, um die SSL-Verarbeitung von Ihren Backend-Servern zu entlasten.

Ein praktischer Ausgangspunkt

Für zwei oder drei identische App-Server beginnen Sie mit einfachem Round Robin, konservativen Proxy-Timeouts und klarem Upstream-Logging. Fügen Sie max_fails und fail_timeout hinzu und testen Sie dann, was passiert, wenn Sie ein Backend stoppen. Warten Sie nicht auf einen echten Vorfall, um zu lernen, wie sich Nginx verhält.

Wenn Anfragen sehr unterschiedliche Zeit in Anspruch nehmen, versuchen Sie least_conn. Wenn ein Server größer ist als die anderen, verwenden Sie Gewichte. Wenn die Anwendung den Sitzungsstatus lokal speichert, beheben Sie das Sitzungsdesign, wenn möglich; verwenden Sie ip_hash nur, wenn Sie eine praktische Brücke benötigen.

Die beste Nginx-Lastverteilungsstrategie ist die, die zu der Art und Weise passt, wie Ihre Anwendung ausfällt. Eine tote VM, ein langsames Backend, eine fehlerhafte Version und ein Datenbankausfall sehen aus Sicht des Proxys alle anders aus. Konfigurieren Sie den Algorithmus und beweisen Sie dann das Ausfallverhalten mit kleinen Tests, bevor Sie das Setup als hochverfügbar bezeichnen.