Be Software Architecture Wise
Be Wise Apps

Read in English · Leer en español · Lire en français

Abschnitt 9.2 von 9.8 · Mittelstufe · 27 Min. Lesezeit

Load Balancing, Zustandslosigkeit und Sessions

Videolektion

5 Min. · auf Englisch gesprochen, mit Untertiteln
Nur anhören (Audio)
Transkript lesen

Willkommen zu Abschnitt neun Punkt zwei. Scale-out gibt Ihnen viele Instanzen. In dieser Lektion geht es darum, was vor ihnen sitzt und wie man die Arbeit so verteilt, dass Nutzer das Kommen und Gehen von Instanzen nie bemerken.

Stellen Sie sich einen Supermarkt vor, in dem eine Mitarbeiterin die Kundschaft zu den Kassen leitet. Die Leute der Reihe nach zu jeder Kasse zu schicken, ist fair, aber naiv. Ein Kunde mit dem Wocheneinkauf für eine sechsköpfige Familie hält eine Kasse auf, während die nächste leer steht. Jeden Kunden zur Kasse mit der kürzesten Schlange zu schicken, funktioniert viel besser. Die Mitarbeiterin muss auch wissen, welche Kassen offen sind, und darf niemanden mehr zu einer Kasse schicken, die gleich schließt, während deren Schlange abgearbeitet wird. Und am besten funktioniert es, wenn jede Kasse jeden Kunden bedienen kann. Das ist die Aufgabe eines Load Balancers.

Ein Load Balancer entscheidet, welche Instanz jede Verbindung oder Anfrage erhält. Er prüft auch, welche Instanzen gesund sind, gibt Clients eine stabile Adresse und terminiert oft TLS. Ein Layer-4-Balancer sieht nur IP-Adressen und Ports. Er wählt einmal pro Verbindung ein Backend und leitet sie weiter, ohne hineinzusehen. Das macht ihn extrem schnell, und er funktioniert mit jedem Protokoll. Ein Layer-7-Proxy versteht HTTP. Er liest jede Anfrage und kann daher jede einzelne routen, wiederholen, begrenzen und protokollieren. Hier routet er nach Pfad: Bestellanfragen zum Bestell-Pool, Speisekarten-Anfragen zum Speisekarten-Pool und Suchen zum Such-Pool. Dieser Unterschied erwischt viele Teams kalt: gRPC schickt viele Anfragen über eine langlebige Verbindung. Ein Layer-4-Balancer wählt einmal pro Verbindung, daher erhalten neue Instanzen womöglich nichts, und gRPC braucht einen Layer-7-Proxy oder Balancing im Client.

Welche Instanz sollte die nächste Anfrage erhalten? Nehmen wir zwei Instanzen, A und B, die jeweils eine Anfrage zur Zeit bearbeiten. Acht Anfragen treffen im Abstand von zehn Millisekunden ein: zuerst ein Bericht, der hundert Millisekunden dauert, dann sieben Speisekarten-Abrufe zu je zehn. Round Robin verteilt die Anfragen der Reihe nach, egal was sie kosten. Also landen drei Speisekarten-Abrufe auf Instanz A und hängen hinter dem Bericht fest. Sie dauern neunzig, achtzig und siebzig Millisekunden. Die Speisekarten-Abrufe dauern im Mittel etwa einundvierzig Millisekunden, der schlechteste neunzig. Instanz B ist währenddessen die halbe Zeit untätig. Least Connections schickt jede Anfrage an die Instanz mit den wenigsten laufenden Anfragen. Der Bericht geht an A, und weil B immer rechtzeitig wieder frei ist, geht jeder Speisekarten-Abruf an B. Jetzt dauert jeder Speisekarten-Abruf zehn Millisekunden, und der Mittelwert aller acht sinkt von etwa neunundvierzig auf etwa einundzwanzig. Round Robin zählt Anfragen. Least Connections zählt laufende Arbeit, sodass neue Anfragen automatisch von einer belasteten oder langsamen Instanz wegfließen.

Warum nicht immer die am wenigsten belastete Instanz im Pool wählen? Weil in einem großen System Dutzende Balancer jeweils nur ihre eigenen Anfragen sehen oder einen Lastbericht, der Sekunden alt ist. Wenn alle die Instanz wählen, die am wenigsten belastet aussah, wählen alle dieselbe. Sie wird überlastet, und die Herde zieht zur nächsten weiter. Michael Mitzenmacher analysierte eine verblüffend einfache Alternative: zwei Instanzen zufällig wählen und die Anfrage an die weniger belastete schicken. Der Zufall verhindert den Herdentrieb, und der Vergleich vermeidet die schlechtesten Instanzen. Das ist heute eine Standardoption in Proxys wie Envoy und eine gute Voreinstellung überall dort, wo es viele Balancer gibt.

Ein Balancer muss wissen, welche Instanzen Traffic annehmen können. Eine Liveness-Prüfung fragt, ob der Prozess unrettbar hängt. Eine Readiness-Prüfung fragt, ob er gerade jetzt Traffic erhalten sollte. Der klassische Fehler ist, die Datenbank aus Liveness-Prüfungen aufzurufen. Ein kurzer Datenbankausfall lässt jede Prüfung scheitern, jede Instanz startet gleichzeitig neu, und der Neustart-Sturm verlängert den Ausfall. Halten Sie Liveness lokal und minimal.

Instanzen verlassen den Pool bei jedem Deployment und jedem Herunterskalieren. Connection Draining macht das zu einem Nicht-Ereignis. Zuerst erhält die Instanz ein Shutdown-Signal. Ihre Readiness-Prüfung schlägt fehl, also schickt der Load Balancer ihr keine neuen Anfragen mehr. Die bereits laufenden Anfragen werden normal abgeschlossen. Ungenutzte Keep-alive-Verbindungen werden geschlossen, sodass Clients sich anderswo neu verbinden. Dann beendet sich der Prozess sauber, bevor seine Grace Period abläuft.

Ältere Anwendungen hielten die Session jedes Nutzers im Arbeitsspeicher eines Servers, daher boten Balancer Sticky Sessions an. Aber die Last wird ungleichmäßig, und wenn diese Instanz ausfällt, verschwinden Warenkörbe, und Menschen werden abgemeldet. Die Abhilfe besteht darin, den Session-Zustand aus der Instanz herauszulösen. Ein kurzlebiges signiertes Token kann die Identität des Nutzers mit jeder Anfrage mitführen. Ein gemeinsamer Session-Speicher bewältigt größeren Zustand und sofortiges Abmelden, auf Kosten eines Netzwerkaufrufs pro Anfrage. Und Zustand, der bestehen bleiben muss, etwa ein Warenkorb, den der Nutzer morgen erwartet, gehört in die Datenbank. Dann kann jede Instanz jede Anfrage bedienen.

Der vollständige Abschnitt behandelt außerdem Hashing und globales Load Balancing mit DNS und Anycast. Als Nächstes betrachten wir in Abschnitt neun Punkt drei, was passiert, wenn Arbeit schneller eintrifft, als sie erledigt werden kann. Bis gleich.

Was Sie wissen müssen

Was ein Load Balancer tut

Ein Load Balancer sitzt zwischen Clients und einem Pool austauschbarer Instanzen und entscheidet für jede Verbindung oder Anfrage, welche Instanz sie erhält. Die Verteilung ist nur ein Teil der Aufgabe. Ein Load Balancer prüft auch, welche Instanzen gesund sind, und schickt den anderen keinen Traffic mehr; er gibt Clients eine stabile Adresse, während Instanzen mit Deployments und Autoscaling kommen und gehen; er terminiert oft TLS, sodass Zertifikate an einer Stelle verwaltet werden; und auf Layer 7 kann er jede Anfrage routen, wiederholen, mit Rate Limits belegen und protokollieren. Er ist daher die Komponente, die horizontale Skalierung für Clients unsichtbar macht.

Load Balancing gibt es auf mehreren Ebenen. Am Edge steht meist ein verwalteter Cloud-Load-Balancer oder ein Paar von Proxys wie HAProxy, NGINX oder Envoy. Zwischen Services wird das Balancing zunehmend clientseitig erledigt: Eine gRPC-Client-Bibliothek oder ein Sidecar-Proxy eines Service Mesh (Abschnitt 6.3) hält die Liste gesunder Instanzen aus der Service Discovery und wählt selbst eine aus, was einen Netzwerk-Hop und eine zentrale Komponente einspart. Unabhängig von der Form kehren dieselben Entscheidungen wieder: was der Balancer sehen kann, welchen Algorithmus er verwendet, wie er entscheidet, ob eine Instanz gesund ist, und wie Instanzen ausscheiden, ohne Arbeit zu verlieren.

Load Balancing auf Layer 4 und Layer 7

Die Layer-Nummern stammen aus dem OSI-Netzwerkmodell. Ein Layer-4-Load-Balancer arbeitet auf der Transportschicht: Er sieht IP-Adressen und TCP- oder UDP-Ports, wählt beim Öffnen einer Verbindung ein Backend und leitet die Pakete dieser Verbindung dann weiter, ohne hineinzusehen. Weil er so wenig tut, ist er extrem schnell und funktioniert mit jedem Protokoll, auch mit Datenbanken, Message Brokern und verschlüsseltem Traffic, den er nicht lesen kann. Googles Maglev und die Network Load Balancer der großen Clouds sind Layer-4-Designs, die Millionen Pakete pro Sekunde verarbeiten.

Ein Layer-7-Load-Balancer ist ein Reverse Proxy, der das Anwendungsprotokoll versteht, fast immer HTTP. Er terminiert die Verbindung des Clients (und meist TLS), liest jede Anfrage und öffnet eigene Verbindungen zu den Backends oder nutzt sie wieder. So kann er nach Pfad, Host, Header oder Cookie routen (/orders/* zum Bestell-Pool, /menus/* zum Speisekarten-Pool), jede Anfrage statt jeder Verbindung verteilen, eine fehlgeschlagene Anfrage auf einer anderen Instanz wiederholen, Rate Limits durchsetzen, Tracing-Header hinzufügen und jede Anfrage mit Status und Latenz protokollieren. Der Preis sind mehr Verarbeitung pro Anfrage, eine Komponente, die die TLS-Schlüssel halten muss, und eine weitere Stelle, an der Timeouts und Wiederholungen konsistent mit dem übrigen System konfiguriert werden müssen (Abschnitt 9.4).

Ein Unterschied erwischt viele Teams kalt. HTTP/2 und gRPC multiplexen viele Anfragen über eine langlebige Verbindung. Ein Layer-4-Balancer wählt ein Backend einmal pro Verbindung, daher schickt ein Client, der eine Verbindung öffnet, alle seine Anfragen an eine Instanz, egal wie viele Instanzen es gibt: Neue Instanzen, die das Autoscaling hinzufügt, erhalten nichts, bis die Clients sich neu verbinden. Das Balancing von gRPC-Traffic braucht daher einen Layer-7-Proxy oder clientseitiges Balancing pro Anfrage.

Eine typische zweistufige Anordnung

Clients verbinden sich mit einem Layer-4-Load-Balancer, der TCP-Verbindungen auf Layer-7-Proxys verteilt; diese terminieren TLS und routen jede HTTP-Anfrage nach Pfad zum richtigen Instanz-PoolClientsLayer-4-LBwählt Backendje VerbindungTCPLayer-7-Proxysterminieren TLSparsen HTTProuten je AnfrageRetry, Rate LimitBestell-API-PoolPfad /orders/*Menü-API-PoolPfad /menus/*Such-PoolPfad /searchsieht: IP-Adressen, Portsschnell, protokollblindsieht: Methode, Pfad,Header, Cookies
Eine Layer-4-Stufe verteilt Verbindungen günstig; eine Layer-7-Stufe liest jede Anfrage und routet sie nach Pfad zum richtigen Pool.

Load-Balancing-Algorithmen

Der Algorithmus entscheidet, welche gesunde Instanz die nächste Verbindung oder Anfrage erhält. Die einfachen ignorieren, wie ausgelastet Instanzen sind; die besseren nutzen ein Maß für die Last; hashbasierte schicken zusammengehörige Anfragen bewusst an dieselbe Stelle. Die Tabelle fasst die üblichen Optionen zusammen.

Gängige Load-Balancing-Algorithmen

AlgorithmusWie er wähltStärkenSchwächen
Round RobinJede Instanz der Reihe nachEinfach, vorhersehbar, kein Zustand über die LastBlind für Anfragekosten und langsame Instanzen
Weighted Round RobinDer Reihe nach, im Verhältnis zu konfigurierten GewichtenMischt Instanzgrößen; fährt neue Versionen schrittweise hoch (Canaries)Gewichte sind statische Schätzungen; weiterhin blind für die tatsächliche Last
ZufälligGleichverteilt zufälligBraucht keinen gemeinsamen Zustand, daher können viele Balancer es unabhängig nutzenZufällig ungleichmäßig, besonders bei wenigen Anfragen
Least Connections (Least Outstanding Requests)Instanz mit den wenigsten laufenden AnfragenPasst sich ungleichen Anfragekosten und langsamen Instanzen anBraucht genaue Zählungen; jeder von vielen Balancern sieht nur seine eigenen; neue Instanzen können überflutet werden
Latenzgewichtet (zum Beispiel EWMA der Antwortzeit)Instanz mit der besten jüngsten Latenz, oft kombiniert mit der LastLenkt schnell von beeinträchtigten Instanzen wegKann oszillieren; eine schnell scheiternde Instanz wirkt attraktiv, wenn Fehler nicht bestraft werden
Hash (Quell-IP, Header, konsistentes Hashing)Derselbe Schlüssel landet immer bei derselben InstanzAffinität für Caches und Sessions; konsistentes Hashing verschiebt wenige Schlüssel, wenn sich der Pool ändertHot Keys überlasten eine Instanz; ungleichmäßige Schlüsselverteilung
Power of Two Random ChoicesZwei Instanzen zufällig wählen; an die weniger belastete sendenFast so gut wie Least Connections, kein Herdentrieb, wenig KoordinationEtwas schlechter als eine perfekte globale Wahl mit perfekter Information

Ausgearbeitetes Beispiel

Ausgearbeitetes Beispiel: warum Least Connections Round Robin schlägt, wenn Anfragekosten sich unterscheiden

Zwei Instanzen, A und B, bearbeiten jeweils eine Anfrage zur Zeit (echte Instanzen haben mehr Threads, aber der Effekt ist im größeren Maßstab derselbe). Acht Anfragen treffen im Abstand von 10 ms ein. Die erste ist ein Umsatzbericht eines Restaurants, der 100 ms kostet; die anderen sieben sind Speisekarten-Abrufe zu je 10 ms.

Round Robin: ZuteilungA erhält die Anfragen 1, 3, 5, 7; B erhält 2, 4, 6, 8, unabhängig von den Kosten
Round Robin auf AR1 läuft von 0 bis 100 ms. R3 (eingetroffen bei 20) wartet und ist bei 110 fertig: 90 ms. R5 (40) ist bei 120 fertig: 80 ms. R7 (60) ist bei 130 fertig: 70 ms
Round Robin auf BR2, R4, R6 und R8 sind jeweils 10 ms nach dem Eintreffen fertig; B ist die halbe Zeit untätig
Ergebnis bei Round RobinSpeisekarten-Abrufe: 90, 80, 70, 10, 10, 10, 10 ms, Mittelwert 290 ÷ 7 ≈ 41 ms, schlechtester Wert 90 ms. Alle acht Anfragen: Mittelwert (100 + 290) ÷ 8 ≈ 49 ms
Least Connections: ZuteilungR1 geht an A (beide untätig). Bei 10 ms hat A 1 laufende Anfrage und B 0, also geht R2 an B; B ist vor jedem nächsten Eintreffen wieder frei, also gehen R3 bis R8 alle an B
Ergebnis bei Least ConnectionsJeder Speisekarten-Abruf dauert 10 ms; der Bericht dauert 100 ms. Mittelwert (100 + 70) ÷ 8 ≈ 21 ms, schlechtester Speisekarten-Abruf 10 ms
WarumRound Robin zählt Anfragen, nicht Arbeit. Least Connections zählt laufende Arbeit, die nach Littles Gesetz (laufend = Rate × Zeit) auf der jeweils langsameren oder stärker belasteten Instanz wächst, sodass neue Anfragen automatisch von ihr wegfließen

Derselbe Effekt tritt immer dann auf, wenn Anfragekosten stark schwanken (Suchen gegenüber Nachschlagen, Exporte gegenüber Lesezugriffen) oder eine Instanz langsamer ist als die anderen, wegen eines Noisy Neighbour, einer Garbage-Collection-Pause oder eines kalten Caches.

Least Connections braucht eine aussagekräftige Zählung. Bei HTTP/2 oder gRPC teilen sich viele Anfragen eine Verbindung, daher zählen Balancer stattdessen offene Anfragen; bei langlebigen WebSocket-Verbindungen misst die Zählung Sessions, nicht Last.

Eine neu gestartete Instanz hat null Verbindungen und kann überflutet werden, bevor ihre Caches warm sind; viele Balancer bieten eine Slow-Start-Phase, die ihren Anteil über etwa eine Minute hochfährt.

Die Power of Two Random Choices

Wenn Least Connections so gut funktioniert, warum nicht immer die global am wenigsten belastete Instanz wählen? Weil es in einem großen System keine einzige, aktuelle Sicht auf die Last gibt. Dutzende Load Balancer oder Tausende clientseitige Balancer sehen jeweils nur ihre eigenen Anfragen oder einen Lastbericht, der einige Sekunden alt ist. Wenn alle die Instanz wählen, die am wenigsten belastet aussah, wählen alle dieselbe, die prompt überlastet wird, und dann ziehen alle zur nächsten weiter: eine Herde, die der Last durch den Pool hinterherjagt.

Michael Mitzenmachers Doktorarbeit (1996), veröffentlicht als The Power of Two Choices in Randomized Load Balancing (IEEE Transactions on Parallel and Distributed Systems, 2001) und aufbauend auf früheren Arbeiten von Azar, Broder, Karlin und Upfal, analysierte eine verblüffend einfache Alternative: zwei Instanzen zufällig wählen und die Anfrage an die weniger belastete der beiden senden. Im klassischen Bälle-in-Körbe-Modell ergibt die zufällige Verteilung von n Anfragen auf n Server einen am stärksten belasteten Server mit etwa log n ÷ log log n Anfragen; mit zwei Zufallsauswahlen sinkt das auf etwa log log n ÷ log 2, eine exponentielle Verbesserung, während eine dritte Auswahl nur noch wenig bringt. Der Zufall verhindert den Herdentrieb, und der Vergleich vermeidet die schlechtesten Instanzen.

Die Power of Two Random Choices ist heute in weit verbreiteten Proxys der Standard oder eine Standardoption: Der Least-Request-Balancer von Envoy vergleicht standardmäßig zwei zufällig gewählte Hosts, und NGINX bietet random two least_conn. Sie ist eine gute Voreinstellung für clientseitiges Balancing in jedem System mit vielen Balancern.

Hashing und Affinität

Manchmal geht es gerade darum, zusammengehörige Anfragen an dieselbe Instanz zu schicken: an einen Cache-Knoten, der die Speisekarte eines Restaurants bereits hält, oder an die Instanz, die eine WebSocket-Session hält. Das Hashen eines Schlüssels (Restaurantkennung, Nutzerkennung, Quell-IP) auf eine Instanz erreicht das. Einfaches hash mod N ordnet die meisten Schlüssel neu zu, sobald sich N ändert, und leert damit jeden Cache im ungünstigsten Moment, während eines Scale-out. Konsistentes Hashing (eingeführt in Abschnitt 7.4) verschiebt nur etwa 1 ÷ N der Schlüssel, wenn eine Instanz hinzukommt oder wegfällt. Seine Schwäche sind Hot Keys: Das beliebteste Restaurant zur Mittagszeit landet auf einer Instanz. Varianten wie Consistent Hashing with Bounded Loads begrenzen den Anteil jeder Instanz und geben den Überschuss an die nächste Instanz auf dem Ring weiter.

Health Checks: Liveness und Readiness

Ein Load Balancer muss wissen, welche Instanzen Traffic annehmen können. Ein aktiver Health Check prüft jede Instanz regelmäßig, zum Beispiel mit GET /healthz alle fünf Sekunden, markiert sie nach einigen aufeinanderfolgenden Fehlschlägen als ausgefallen und nach einigen Erfolgen wieder als verfügbar. Passive Prüfungen, oft Outlier Detection genannt, beobachten den echten Traffic und werfen eine Instanz hinaus, die etwa fünf aufeinanderfolgende Serverfehler liefert. Beide ergänzen sich: Aktive Prüfungen finden Instanzen, die keinen Traffic erhalten, passive Prüfungen reagieren auf Fehler, die eine triviale Prüfung nicht auslöst.

Kubernetes hat eine wichtige Unterscheidung explizit gemacht. Eine Liveness Probe fragt, ob der Prozess unrettbar hängt (Deadlock, Speichermangel in einer Schleife); schlägt sie fehl, wird die Instanz neu gestartet. Eine Readiness Probe fragt, ob die Instanz gerade jetzt Traffic erhalten sollte; schlägt sie fehl, wird die Instanz aus dem Load Balancer genommen, läuft aber weiter, etwa während sie beim Start ihre Caches aufwärmt oder während sie Last abwirft. Eine dritte, die Startup Probe, hält die anderen beiden zurück, während eine langsame Anwendung startet.

Der klassische Fehler besteht darin, in diesen Probes gemeinsame Abhängigkeiten zu prüfen. Ruft die Liveness Probe jeder Instanz die Datenbank auf, lässt ein kurzer Datenbankausfall jede Probe scheitern, der Orchestrator startet alle Instanzen gleichzeitig neu, und der Neustart-Sturm (kalte Caches, Tausende neuer Verbindungen) verlängert den Ausfall noch lange, nachdem die Datenbank wieder da ist. Readiness-Prüfungen, die eine gemeinsame Abhängigkeit einschließen, können ebenso die ganze Flotte als nicht bereit markieren und Teilfehler in einen Totalausfall verwandeln. Halten Sie Liveness lokal und minimal, lassen Sie Readiness die eigene Fähigkeit der Instanz zum Bedienen widerspiegeln, und behandeln Sie Ausfälle von Abhängigkeiten mit den Mustern aus Abschnitt 9.4. Load Balancer schützen sich gegen den Extremfall, indem sie offen ausfallen (Fail Open): Ein AWS Application Load Balancer routet zu allen Zielen, wenn alle ihre Health Checks nicht bestehen, und Envoy ignoriert den Gesundheitsstatus, wenn der Anteil gesunder Hosts unter eine Panikschwelle fällt (standardmäßig 50 %), mit der Begründung, dass Traffic an zweifelhafte Instanzen besser ist als Traffic ins Nichts.

Connection Draining und kontrolliertes Herunterfahren

Instanzen verlassen den Pool ständig: Jedes Deployment ersetzt sie, das Autoscaling entfernt sie, und die Plattform verschiebt sie bei Wartungsarbeiten. Wird eine Instanz einfach beendet, scheitert jede Anfrage, die sie gerade bearbeitet, und Clients sehen Fehler bei einem eigentlich routinemäßigen Release. Connection Draining (bei AWS Deregistration Delay genannt) nimmt die Instanz aus der Rotation des Balancers, sodass sie keine neuen Anfragen erhält, wartet, bis die bereits laufenden Anfragen abgeschlossen sind, und lässt sie erst dann stoppen.

Die Instanz muss mitwirken. Wenn sie ihr Shutdown-Signal erhält, sollte sie ihre Readiness-Prüfung nicht mehr bestehen, laufende Anfragen weiter bedienen, Clients auf Keep-alive-Verbindungen mitteilen, sich anderswohin zu wenden (ein Connection: close-Header bei HTTP/1.1, ein GOAWAY-Frame bei HTTP/2), Hintergrundarbeit abschließen oder zurückgeben und sich vor dem Ende der Grace Period beenden. In Kubernetes geschehen das Entfernen eines Pods aus dem Load Balancer und das Senden von SIGTERM gleichzeitig, daher kann kurz nach dem Signal noch eine Anfrage eintreffen; eine kurze Pre-Stop-Verzögerung von einigen Sekunden deckt diese Race Condition ab. Die Standard-Grace-Period beträgt 30 Sekunden, danach wird der Prozess beendet. Lang laufende Anfragen (große Uploads, Streaming-Antworten, WebSockets) brauchen entweder eine längere Grace Period oder ein Protokoll, über das sich Clients ohne Arbeitsverlust anderswo neu verbinden.

Kontrolliertes Herunterfahren in fünf Schritten

Zeitachse eines kontrollierten Shutdowns: Die Instanz verlässt den Load Balancer, besteht Readiness nicht mehr, beendet laufende Anfragen, schließt ungenutzte Verbindungen und endet erst dannDrain-Phase, begrenzt durch eine Grace Period (zum Beispiel 30 s)Zeit1 Shutdown-Signal (SIGTERM)o. Deregistrierung2 Readiness fällt;LB schickt keineneuen Anfragen3 laufendeAnfragen endennormal4 freie Keep-alive-Verb. zu(HTTP/2 GOAWAY)5 Prozessendetsauber
Draining macht das Entfernen einer Instanz von einer Fehlerquelle zu einem Nicht-Ereignis: Es kommt keine neue Arbeit an, vorhandene Arbeit wird abgeschlossen, dann beendet sich der Prozess.

Sticky Sessions und ihre Probleme

Ältere Webanwendungen hielten die Session jedes Nutzers (wer angemeldet ist, was im Warenkorb liegt) im Arbeitsspeicher des Servers, der sie erstellt hatte. Hinter einem Load Balancer funktioniert das nur, wenn alle Anfragen eines Nutzers zu diesem Server zurückgehen; deshalb bieten Balancer Sticky Sessions (Session-Affinität), meist über ein Cookie, das die Instanz benennt. Stickiness ist ein schneller Weg, eine zustandsbehaftete Anwendung hinter einen Balancer zu stellen, und sie hat eine berechtigte Verwendung als Performance-Hinweis, etwa um einen Nutzer auf einer Instanz zu halten, deren lokaler Cache seine Daten bereits enthält.

Als Mechanismus für Korrektheit macht sie Ärger. Die Last wird ungleichmäßig, weil einige intensive Nutzer oder ein großes Büro hinter einer IP-Adresse an einer Instanz festhängen, egal wie belastet sie ist. Fällt eine Instanz aus, verlieren ihre Nutzer ihre Sessions: Warenkörbe verschwinden, und Menschen werden abgemeldet. Herunterskalieren und jedes Deployment bewirken dasselbe, sofern Sessions nicht per Draining beendet werden, was so lange dauern kann wie die längste Session. Neue Instanzen, die das Autoscaling hinzufügt, erhalten nur neue Nutzer und entlasten die überlasteten Instanzen daher nur langsam. Und das System ist nicht mehr zustandslos im Sinne von Abschnitt 9.1, sodass jedes dieser Probleme beim Failover zwischen Zonen oder Regionen wieder auftaucht.

Den Session-Zustand auslagern

Die Abhilfe besteht darin, den Session-Zustand aus der Instanz herauszulösen, damit jede Instanz jede Anfrage bedienen kann. Es gibt drei Hauptoptionen, oft kombiniert. Ein clientseitiges Token trägt den Zustand mit jeder Anfrage: ein signiertes (und bei Bedarf verschlüsseltes) Cookie oder ein Token wie ein JWT mit der Identität des Nutzers und ein paar Daten. Server brauchen überhaupt keinen Speicher, aber Tokens müssen klein bleiben, lassen sich vor ihrem Ablauf nicht leicht widerrufen und müssen gegen Manipulation und Diebstahl geschützt werden (Abschnitt 8.3). Ein gemeinsamer Session-Speicher, typischerweise Redis oder Memcached, hält Sessions in einem schnellen externen Speicher, adressiert über eine Session-Kennung im Cookie; er bewältigt größeren Zustand und sofortiges Abmelden, auf Kosten eines Netzwerkaufrufs pro Anfrage und einer Abhängigkeit, die selbst hochverfügbar sein muss. Eine Datenbank eignet sich für Zustand, der alles überstehen muss, etwa einen Warenkorb, den der Nutzer morgen wiederzufinden erwartet; das sind eigentlich Geschäftsdaten und keine Session-Daten.

Eine praktische Aufteilung für die Essenslieferplattform: die Identität in einem kurzlebigen signierten Token, der Warenkorb in der Datenbank des Bestellservice (Nutzer wechseln vom Smartphone zum Laptop) und flüchtiger UI-Zustand im Client. Keine Instanz behält etwas, das eine spätere Anfrage braucht, daher kann der Load Balancer Least-Request-Balancing ohne Affinität verwenden, und Instanzen lassen sich nach Belieben hinzufügen, entfernen und ersetzen.

Globales Load Balancing: DNS und Anycast

Sobald ein System in mehreren Regionen läuft, muss etwas jeden Nutzer in eine geeignete schicken: die nächstgelegene gesunde Region oder die Region, die seine Daten hält. Globales Load Balancing erfolgt meist auf eine von zwei Arten, oft kombiniert.

DNS-basiertes Global Server Load Balancing nutzt einen autoritativen Nameserver, der denselben Namen mit unterschiedlichen Adressen beantwortet, abhängig davon, woher die Anfrage kommt, vom Gesundheitszustand jeder Region und von konfigurierten Gewichten: Ein Nutzer in Lissabon erhält die Adresse der EU-Region, ein Nutzer in Ohio die der US-Region. Es braucht kein besonderes Netzwerk, ist aber grob und reagiert langsam. Antworten werden von Resolvern mindestens für die Time to Live des Eintrags (oft 30 bis 300 Sekunden) und manchmal länger zwischengespeichert, daher kommen nach dem Ausfall einer Region einige Nutzer noch minutenlang dort an. Der Nameserver sieht den Standort des Resolvers statt den des Nutzers, wobei die Erweiterung EDNS Client Subnet (RFC 7871) es Resolvern erlaubt, einen Teil der Client-Adresse weiterzugeben.

Bei Anycast wird dieselbe IP-Adresse über das Routing-Protokoll des Internets (BGP) von vielen Standorten aus angekündigt, und die Pakete jedes Nutzers werden an den Standort zugestellt, der routingtechnisch am nächsten liegt. Failover bedeutet, die Ankündigung eines ausgefallenen Standorts zurückzuziehen, was schnell wirkt, ohne auf DNS-Caches zu warten. Anycast wird seit Langem für DNS-Root- und Resolver-Dienste genutzt; CDNs und die globalen Load Balancer der großen Cloud-Anbieter nutzen es, um Nutzer zu einem nahen Edge-Standort zu bringen, an dem Layer-7-Proxys die Verbindung terminieren und Anfragen über das Netzwerk des Anbieters an eine gesunde Region weiterleiten. Multi-Region-Design, Datenplatzierung und Failover sind Thema von Abschnitt 10.6.

DNS-basiertes Load Balancing und Anycast

Zwei Arten des globalen Load Balancing: DNS-basiert, wobei der Nameserver die Adresse einer nahen gesunden Region liefert, und Anycast, wobei eine Adresse von vielen Edge-Standorten angekündigt wird und das Routing jeden Nutzer zum nächstgelegenen bringtDNS-basiertNutzer LissabonResolverGSLB-Name-server1 Abfrageantwortet nach Ortund Zustand; TTL 30 sEU-RegionUS-Region2 verbindenmit EU-Adr.AnycastNutzer LissabonNutzer OhioEdge Madrid203.0.113.10Edge Chicago203.0.113.10gleiche Adr.von jedem Ort;BGP wählt nächstenEU-RegionUS-Region
DNS lenkt Nutzer, indem es mit unterschiedlichen Adressen antwortet; Anycast gibt allen dieselbe Adresse und lässt das Internet-Routing sie zum nächstgelegenen Standort bringen.

Prüfen Sie Ihr Verständnis

Warum erhält ein neu hinzugefügter gRPC-Server hinter einem Layer-4-Load-Balancer womöglich keinen Traffic?

Layer-4-Balancing wählt ein Backend pro Verbindung, und gRPC-Clients halten eine langlebige HTTP/2-Verbindung; vorhandene Clients schicken ihm also keine Anfragen, bis sie sich neu verbinden.

Was ist der Unterschied in der Wirkung zwischen einer fehlgeschlagenen Liveness Probe und einer fehlgeschlagenen Readiness Probe?

Eine fehlgeschlagene Liveness Probe lässt den Orchestrator die Instanz neu starten; eine fehlgeschlagene Readiness Probe nimmt sie nur aus dem Load Balancer, bis sie wieder besteht.

Warum schneiden viele unabhängige Load Balancer schlecht ab, die jeweils die am wenigsten belastete Instanz wählen?

Sie handeln auf Basis derselben veralteten Sicht und wählen alle dieselbe Instanz, die sie nacheinander überlasten; die bessere von zwei zufälligen Instanzen zu wählen, vermeidet diesen Herdentrieb.

Nennen Sie zwei Probleme, die entstehen, wenn Sessions mit Sticky Sessions im Arbeitsspeicher der Instanz gehalten werden.

Nutzer verlieren ihre Sessions, wenn die Instanz ausfällt oder ersetzt wird, und die Last wird ungleichmäßig, weil intensive Nutzer an einer Instanz festhängen.

Weiter · 9.3Queues und BackpressureWie Queues Lastspitzen in Rückstände verwandeln, Competing Consumers, die Dimensionierung von Queues mit Littles Gesetz, Backpressure, Load Shedding und Priorisierung, Token Bucket und andere Rate Limiter, HTTP 429 mit Retry-After und Dead Letter Queues.Weiter →

Quellen und weiterführende Literatur

Haben Sie eine Frage zu diesem Abschnitt? ArchitectBot, der KI-Lernassistent des Kurses, antwortet zuerst aus dem Kurs.

Fragen: „Wir betreiben acht Instanzen hinter einem Load Balancer mit Sticky Sessions und verlieren bei jedem Deployment die Warenkörbe der Nutzer. Wie stellen wir sicher auf zustandslose Instanzen um?“