Letztes Jahr veröffentlichte das Uber Engineering-Team einen Artikel über seinen neuen Lastabwurfmechanismus, der für seine Microservice-Architektur entwickelt wurde.
Dieser Artikel ist aus verschiedenen Perspektiven sehr interessant. Deshalb habe ich mir beim Lesen einige Notizen gemacht, um mein Verständnis festzuhalten und Dinge aufzuschreiben, auf die ich später tiefer eingehen möchte, falls ich am Ende keine Antworten bekomme. Ich habe mehrfach festgestellt, dass dies für mich der beste Weg ist, neue Dinge zu lernen.
Was mich von Anfang an faszinierte, war der Verweis auf jahrhundertealte Prinzipien, die bei der Entwicklung dieser Lösung zum Einsatz kamen. Das ist etwas, was ich liebe – Konzepte/Ideen aus verschiedenen Bereichen zu übernehmen und sie anzupassen, um ein Problem in einem anderen Bereich zu lösen.
Wenn Sie an Ausfallsicherheit und Stabilität des Systems interessiert sind, empfehle ich Ihnen, auch das hervorragende Buch „Release It!“ zu lesen. von Michael T. Nygard.
Es ist ein Oldie, aber ein Leckerbissen – ein Buch, das sich mit Strategien, Mustern und praktischen Anleitungen für den Aufbau belastbarer und stabiler Softwaresysteme befasst und dabei den Schwerpunkt darauf legt, wie man mit Ausfällen effektiv umgeht.
Uber hat eine neue Lastabwurflösung namens Cinnamon implementiert, die einen PID-Regler (den jahrhundertealten Mechanismus) nutzt, um basierend auf der aktuellen Auslastung des Dienstes und der Anforderungspriorität zu entscheiden, welche Anfragen von einem Dienst verarbeitet oder verworfen werden sollen.
Es erfordert keine Optimierung auf Serviceebene (obwohl ich dazu eine Frage hatte), ist automatisch anpassbar und viel effizienter als die vorherige Lösung QALM. Denken Sie auch daran, dass die Microservices-Architektur von Uber nichts für schwache Nerven ist …
Ein PID-Regler ist ein Instrument, das in industriellen Steuerungsanwendungen zur Regelung von Temperatur, Durchfluss, Druck, Geschwindigkeit und anderen Prozessvariablen verwendet wird. PID-Regler (Proportional-Integral-Derivat) nutzen einen Regelkreis-Rückkopplungsmechanismus zur Steuerung von Prozessvariablen und sind die genauesten und stabilsten Regler.
Wenn Sie weitere Informationen zu diesem jahrhundertealten Konzept wünschen, besuchen Sie Wikipedia.
Nun zurück zum Artikel. PID steht für Proportional, Integral und Derivativ. In ihrem Fall verwenden sie eine Komponente, die als PID-Regler bekannt ist, um den Zustand eines Dienstes (Eingabeanforderungen) basierend auf drei Komponenten (oder Maßnahmen) zu überwachen.
Der Begriff „proportional“ gibt an, dass die ergriffene Maßnahme proportional zum aktuellen Fehler ist. Vereinfacht ausgedrückt bedeutet dies, dass die angewendete Korrektur direkt proportional zur Differenz zwischen Soll-Zustand und Ist-Zustand ist. Wenn der Fehler groß ist, ist die Korrekturmaßnahme proportional groß.
Wenn ein Endpunkt überlastet ist, beginnt die Hintergrund-Goroutine, den Zu- und Abfluss von Anforderungen in die Prioritätswarteschlange zu überwachen.
Daher passt die Proportionalkomponente (P) im Lastabwurf die Abwurfrate basierend darauf an, wie weit die aktuelle Warteschlangengröße vom Ziel oder der gewünschten Warteschlangengröße entfernt ist. Wenn die Warteschlange größer als gewünscht ist, kommt es zu mehr Abwurf; ist es kleiner, verringert sich der Haarausfall.
Das ist mein Verständnis davon.
Die Aufgabe des PID-Reglers besteht darin, die Anzahl der in der Warteschlange befindlichen Anforderungen zu minimieren, während die Aufgabe des Auto-Tuners darin besteht, den Durchsatz des Dienstes zu maximieren, ohne die Antwortlatenzen (zu stark) zu beeinträchtigen.
Während der Text „Integral (I)“ im Zusammenhang mit der Warteschlangengröße nicht ausdrücklich erwähnt, weist er darauf hin, dass die Rolle des PID-Reglers darin besteht, die Anzahl der in der Warteschlange befindlichen Anforderungen zu minimieren. Die Minimierung von Anfragen in der Warteschlange steht im Einklang mit dem Ziel der Integral-Komponente, akkumulierte Fehler im Laufe der Zeit zu beheben.
Um festzustellen, ob ein Endpunkt überlastet ist, verfolgen wir, wann die Anforderungswarteschlange das letzte Mal leer war. Wenn sie in den letzten etwa 10 Sekunden nicht geleert wurde, betrachten wir den Endpunkt als überlastet (inspiriert von Facebook).
Im Lastabwurf kann es mit Entscheidungen im Zusammenhang mit dem historischen Verhalten der Anforderungswarteschlange verknüpft sein, beispielsweise mit der Zeit, seit sie das letzte Mal leer war.
Ehrlich gesagt ist mir das nicht ganz klar. Es ist ein bisschen frustrierend, muss ich sagen. Während sie die Nutzung eines jahrhundertealten Mechanismus erwähnen, wäre es hilfreich gewesen, wenn sie explizit angegeben hätten, welcher Teil womit korrespondiert oder wie er funktioniert. Ich möchte den Wert ihres erstaunlichen Artikels nicht schmälern. Das ist nur meine Schimpftirade ... Schließlich bin ich Franzose ... ;)
Ich denke, dieser ist leichter zu identifizieren.
In einem klassischen PID-Regler (Proportional-Integral-Derivativ) ist die Aktion „Ableitung (D)“ besonders nützlich, wenn der Regler das zukünftige Verhalten des Systems basierend auf der aktuellen Änderungsrate des Fehlers vorhersehen soll. Es hilft, Schwingungen zu dämpfen und die Stabilität des Systems zu verbessern.
Im Zusammenhang mit dem im Artikel erwähnten Lastabwurf und dem PID-Regler wird die Derivative-Komponente wahrscheinlich verwendet, um zu beurteilen, wie schnell sich die Anforderungswarteschlange füllt. Auf diese Weise hilft es bei der Entscheidungsfindung, die darauf abzielt, ein stabiles System aufrechtzuerhalten und plötzliche oder unvorhersehbare Änderungen zu verhindern.
Die Ablehnungskomponente hat zwei Aufgaben: a) herauszufinden, ob ein Endpunkt überlastet ist, und b) bei Überlastung eines Endpunkts einen Prozentsatz der Anfragen zu verwerfen, um sicherzustellen, dass die Anfragewarteschlange so klein wie möglich ist. Wenn ein Endpunkt überlastet ist, beginnt die Hintergrund-Goroutine, den Zu- und Abfluss von Anforderungen in die Prioritätswarteschlange zu überwachen. Basierend auf diesen Zahlen ermittelt es mithilfe eines PID-Reglers das Verhältnis der zu verwerfenden Anfragen. Der PID-Regler ist sehr schnell (da nur sehr wenige Iterationen erforderlich sind) beim Finden des richtigen Levels und sobald die Anforderungswarteschlange geleert wurde, stellt der PID sicher, dass wir das Verhältnis nur langsam reduzieren.
Im genannten Zusammenhang wird der PID-Regler verwendet, um das Verhältnis der zu verwerfenden Anfragen bei Überlastung eines Endpunkts zu bestimmen und den Zu- und Abfluss von Anfragen zu überwachen. Die abgeleitete Komponente des PID-Reglers, die auf die Änderungsrate reagiert, ist implizit an der Beurteilung beteiligt, wie schnell die Anforderungswarteschlange gefüllt oder geleert wird. Dies hilft bei dynamischen Entscheidungen zur Aufrechterhaltung der Systemstabilität.
Im Zusammenhang mit der Bestimmung der Überlastung könnte die Integralkomponente mit der Verfolgung verbunden sein, wie lange sich die Anforderungswarteschlange in einem nicht leeren Zustand befand. Dies steht im Einklang mit der Idee, das Integral des Fehlersignals über die Zeit zu akkumulieren.
„Integral – basierend darauf, wie lange sich die Anfrage in der Warteschlange befindet …“
Die derivative Komponente hingegen hängt von der Änderungsrate ab. Es reagiert darauf, wie schnell sich der Status der Anforderungswarteschlange ändert.
„Ableitung – Ablehnung basierend darauf, wie schnell sich die Warteschlange füllt …“
Die Integralkomponente betont die Dauer des nicht leeren Zustands, während die Derivativkomponente die Geschwindigkeit berücksichtigt, mit der sich die Warteschlange ändert.
Am Ende des Spiels bestimmen sie anhand dieser drei Maßnahmen die Vorgehensweise für eine Anfrage.
Die Frage, die ich habe, ist, wie sie diese drei Komponenten kombinieren, wenn überhaupt. Ich bin gespannt, wie sie sie auch überwachen.
Dennoch denke ich, dass ich die Idee habe …
Der Endpunkt im Edge wird mit der Priorität der Anfrage versehen und diese wird vom Edge über Jaeger an alle Downstream-Abhängigkeiten weitergegeben. Durch die Weitergabe dieser Informationen wissen alle Dienste in der Anforderungskette, wie wichtig die Anforderung ist und wie kritisch sie für unsere Benutzer ist.
Der erste Gedanke, der mir in den Sinn kommt, ist, dass es sich nahtlos in eine Service-Mesh-Architektur integrieren lässt.
Ich schätze das Konzept, die Ablaufverfolgung und Header verteilter Dienste zur Weitergabe der Anforderungspriorität einzusetzen. Warum sollten Sie sich in diesem Sinne für eine gemeinsam genutzte Bibliothek entscheiden, bei der diese Abhängigkeit zu jedem Mikrodienst hinzugefügt wird, anstatt sie außerhalb des Dienstes zu platzieren, vielleicht als Istio-Plugin? In Anbetracht der Vorteile, die es bietet: unabhängige Release-/Bereitstellungszyklen, mehrsprachige Unterstützung usw.
Hier sind einige zusätzliche Gedanken:
Nun, ich bin voreingenommen, da ich kein großer Fan von gemeinsam genutzten Bibliotheken bin, schon allein deshalb, weil ich denke, dass sie den Release-/Bereitstellungsprozess erschweren. Ich bin mir jedoch nicht sicher, ob ein dienstspezifischer Konfigurationsaspekt berücksichtigt werden muss. Vielleicht konfigurieren sie, wie lange der Dienst warten soll, bis er mit der Verarbeitung einer Abfrage beginnt und diese abschließt?
Ein Aspekt, der es wert ist, getestet zu werden, ist vielleicht der Entscheidungsprozess des Ejektors.
Soweit ich weiß, wird anhand des PID-Reglers, der für den Dienst lokalisiert ist, bestimmt, ob eine Anfrage abgelehnt werden soll. Gibt es eine Option für einen globaleren Ansatz? Wenn beispielsweise bekannt ist, dass einer der Downstream-Dienste in der Pipeline überlastet ist (aufgrund seines eigenen PID-Reglers), könnte dann ein Upstream-Dienst beschließen, die Anfrage abzulehnen, bevor sie diesen überlasteten Dienst erreicht (was n Schritte weiter unten sein könnte). Weg)?
Diese Entscheidung könnte auf dem vom PID-Regler oder dem Auto-Tuner des nachgeschalteten Dienstes zurückgegebenen Wert basieren.
Jetzt denke ich über verschiedene erwähnte Aspekte nach, während sie den Artikel abschließen und einige Zahlen liefern, um die Effizienz ihres Systems zu veranschaulichen, was ziemlich beeindruckend ist
Sie erwähnen irgendwann, dass „jede Anfrage eine Zeitüberschreitung von 1 Sekunde hat.“
Wir führen Tests von 5 Minuten durch, bei denen wir eine feste Menge an RPS (z. B. 1.000) senden, wobei 50 % des Datenverkehrs Tier 1 und 50 % Tier 5 sind. Jede Anfrage hat ein Timeout von 1 Sekunde.
In verteilten Systemen ist es üblich, einer Anfrage eine bestimmte Ablaufzeit oder Frist zuzuordnen, wobei jeder Dienst entlang des Verarbeitungspfads für die Durchsetzung dieser Frist verantwortlich ist. Wenn die Ablaufzeit erreicht ist, bevor die Anfrage abgeschlossen ist, hat jeder Dienst in der Kette die Möglichkeit, die Anfrage abzubrechen oder abzulehnen.
Ich gehe davon aus, dass diese 1-Sekunden-Zeitüberschreitung mit der Anfrage verbunden ist und jeder Dienst, je nachdem, wo wir uns in dieser Frist befinden, entscheiden kann, die Anfrage abzubrechen. Dies ist eine globale Kennzahl, da sie über die Dienste aggregiert wird. Ich denke, es deckt sich mit dem Punkt, den ich zuvor angesprochen habe, nämlich einen globalen Überblick über den gesamten Systemzustand oder die Abhängigkeiten zu haben, um zu entscheiden, die Anfrage so schnell wie möglich abzubrechen, wenn sie aufgrund eines Ausfalls eines Dienstes nicht abgeschlossen werden kann Weg.
Könnte der „Zustand“ der nachgeschalteten Dienste (bestehend aus Daten von ihren lokalen PID-Reglern) als an die Antworten angehängte Header zurückgegeben und zum Aufbau eines weiterentwickelten Schutzschalter-/frühpräventiven Abschaltmechanismus verwendet werden?
Abschließend bin ich gespannt darauf, mehr über den vorherigen Ansatz zu erfahren, da er, basierend auf der Beschreibung in diesem Dokument, vernünftig zu sein scheint.
Wenn Sie die Kennzahlen Goodput und Latenzen untersuchen, besteht kein Zweifel daran, welches, QALM oder Cinnamon, die beste Leistung erbringt. Beachten Sie, dass im Artikel ein Link zum QALM-Ansatz erwähnt wird. Wahrscheinlich sollte man da anfangen ;)
Wie immer sind diese Ansätze nicht jedermanns Sache. Die Architektur und Auslastung von Uber sind eine Art Eigenes. Ich kann es kaum erwarten, die nächsten Artikel dieser Serie zu lesen, insbesondere um mehr über den PID-Regler und den Auto-Tuner zu erfahren.
Mit Cinnamon haben wir einen effizienten Lastabwurf entwickelt, der jahrhundertealte Techniken nutzt, um dynamisch Schwellenwerte für die Ablehnung und Schätzung der Kapazität der Dienste festzulegen. Es löst die Probleme, die uns bei QALM (und damit jedem CoDel-basierten Lastabwurf) aufgefallen sind, nämlich, dass Cinnamon in der Lage ist:
- Finden Sie schnell eine stabile Ablehnungsquote
- Passen Sie die Kapazität des Dienstes automatisch an
- Kann verwendet werden, ohne dass Konfigurationsparameter festgelegt werden müssen
- Es fallen sehr geringe Gemeinkosten an
Das Interessante an diesem Ansatz ist, dass er alle zu verarbeitenden Anforderungen berücksichtigt, um zu entscheiden, was mit jeder neuen Eingabeanforderung geschehen soll, da er eine (Prioritäts-)Warteschlange verwendet. Wie bereits erwähnt, bin ich gespannt, ob der Mechanismus auch den Zustand aller abhängigen Dienste auf der Grundlage derselben PID-Maßnahmen berücksichtigen könnte …
Es gibt noch weitere interessante Aspekte in diesem Artikel, etwa wie sie die Wirkung ihrer Strategien messen und den Vergleich mit dem vorherigen Ansatz. Es bedarf jedoch keiner ausführlicheren Anmerkungen meinerseits als das bereits Dargelegte. Daher empfehle ich Ihnen dringend, den Originalartikel zu lesen.
Fanden Sie diesen Artikel nützlich? Folgen Sie mir auf Linkedin , Hackernoon und Medium ! Bitte 👏 diesen Artikel, um ihn zu teilen!
Auch hier veröffentlicht.