Im vorherigen Artikel haben wir zwei Prozessmodelle für Serverless besprochen: Run-to-Completion und langlebige Prozesse. Der Hauptunterschied zwischen diesen Modellen liegt darin, ob die Funktionsinstanz nach der Ausführung sofort beendet wird. Außerdem haben wir zwei Szenarien untersucht: Datenorchestrierung und Serviceorchestrierung.
Du fragst dich vielleicht, ob diese Szenarien mit langlebigen Prozessen umgesetzt werden können. Die Antwort lautet ja, aber es ist wichtig zu beachten, dass Run-to-Completion die reinste Form von Serverless ist. Was ist also die zugrundeliegende Logik?
Um dies vollständig zu verstehen, müssen wir ein entscheidendes Konzept in der Entwicklung komplexer Internetanwendungsarchitekturen einführen: Skalierung, das Thema dieses Artikels.
Stell dir vor, 200 Benutzer greifen gleichzeitig auf die index.html-Startseite deiner lokal entwickelten Webanwendung zu. Was passiert mit deiner lokalen Webserver-Instanz?
Beschreiben wir den Zustand deines PCs. Erstens werden 200 TCP/IP-Verbindungen zwischen den Clients und deinem PC aufgebaut, was er gerade noch bewältigen kann. Dann initiieren alle 200 Clients gleichzeitig HTTP-“GET/”-Anfragen. Der Hauptprozess deines Webservers erstellt “Anzahl der CPU-Kerne -1” untergeordnete Prozesse gleichzeitig, um diese Anfragen zu bearbeiten. Beachte, dass wir einen vom CPU-Kern abziehen, um einen für den Hauptprozess freizuhalten.
Bei einem 4-Kern-Prozessor werden beispielsweise drei untergeordnete Prozesse erstellt, die drei Client-Anfragen gleichzeitig bearbeiten, während die restlichen Anfragen in die Warteschlange gestellt werden. Die untergeordneten Prozesse beginnen mit der Verarbeitung der “GET/”-Anfragen, gleichen die Routing-Regeln ab, rufen die entsprechende Steuerfunktion auf und geben index.html an die Clients zurück. Sobald ein untergeordneter Prozess die index.html-Datei gesendet hat, wird er vom Hauptprozess recycelt und ein neuer erstellt, um die nächste Anfrage zu bearbeiten, bis alle Anfragen verarbeitet sind.
Wenn du das verstehst, wird die nächste Frage einfach. Wie verbessern wir die Verarbeitungsgeschwindigkeit unserer Client-Warteschlange?
Vertikale vs. Horizontale Skalierung
Eine offensichtliche Lösung ist die Erhöhung der Anzahl der CPU-Kerne. Wir können dies durch Aufrüsten der Konfiguration einer einzelnen Maschine erreichen, z.B. von 4 auf 8 Kerne, was zu 7 gleichzeitigen untergeordneten Prozessen führt.
Neben der direkten Erhöhung der CPU-Kerne können wir weitere Maschinen hinzufügen (jeweils mit 4 Kernen). Indem wir 500 Clients auf die beiden Maschinen verteilen, können wir die Anzahl der gleichzeitigen untergeordneten Prozesse ebenfalls auf 6 erhöhen.
Die Erhöhung oder Verringerung der Leistung einer einzelnen Maschine ist vertikale Skalierung, die oft mit einem steilen Kostenanstieg bei steigender Leistung einhergeht. Daher ist bei diesem Ansatz eine sorgfältige Abwägung erforderlich. Andererseits ist die Erhöhung oder Verringerung der Anzahl der Maschinen die horizontale Skalierung, ein kostengünstigerer Ansatz und unsere Standardskalierungsmethode.
Jetzt fügen wir noch etwas Komplexität hinzu. Während index.html eine einzelne Datei ist, wie sieht es mit Daten aus? Egal ob vertikal oder horizontal skaliert wird, wir müssen die Maschinen neu starten. In unserem To-do-Listen-Beispiel werden die Daten im Arbeitsspeicher gespeichert und bei jedem Neustart zurückgesetzt. Wie bewahren wir also unsere Daten während der Skalierung?
Zustandsbehaftet vs. Zustandslos
Knoten in einer Netzwerktopologie können basierend darauf, ob sie Zustände speichern, als zustandsbehaftet oder zustandslos kategorisiert werden. Zustandsbehaftete Knoten behalten Zustände bei, d.h. sie speichern Daten. Daher erfordern sie besondere Aufmerksamkeit, verlangen Stabilität und Widerstandsfähigkeit gegen häufige Änderungen. Datenbanken verwenden beispielsweise typischerweise eine Master-Slave-Struktur, sodass bei Problemen mit dem Master-Knoten sofort auf einen Slave-Knoten umgeschaltet werden kann, um eine kontinuierliche Dienstverfügbarkeit zu gewährleisten.
Zustandslose Knoten hingegen speichern keine Zustände oder halten nur vorübergehend unzuverlässige Daten. Aufgrund ihrer Zustandslosigkeit können zustandslose Knoten horizontal skaliert werden, um hohe Parallelität zu bewältigen und bei fehlendem Traffic auf null herunterskaliert werden (kommt dir bekannt vor?). Zustandsbehaftete Knoten können dies nicht. In Szenarien mit großen Verkehrsschwankungen zwischen Spitzen- und Nebenzeiten müssen wir zustandsbehaftete Knoten so entwerfen, dass sie Spitzenlasten bewältigen, während die Betriebskosten auch in verkehrsarmen Zeiten erhalten bleiben.
Eine Datenbank ist ein typischer zustandsbehafteter Knoten, da sie die To-do-Aufgaben der Benutzer dauerhaft speichert. Auch ein Load Balancer ist zustandsbehaftet, ähnlich wie der Hauptprozess, der die Client-Warteschlange in unserem Gedankenexperiment verwaltet. Er muss Client-Verbindungen speichern, um die von unserer Webanwendung verarbeiteten Ergebnisse an die Clients zurückzugeben.
Kehren wir zu unseren Prozessmodellen zurück. Run-to-Completion ist von Natur aus zustandslos, da es nach der Ausführung beendet wird, sodass es unmöglich ist, es allein für die dauerhafte Datenspeicherung zu verwenden. Langlebige Prozesse hingegen sind von Natur aus zustandsbehaftet, da ihr Hauptprozess nicht beendet wird, sodass er einige Werte speichern kann.
Allerdings kann der Cloud-Anbieter selbst dann, wenn wir Werte im Hauptprozess eines langlebigen Prozesses speichern, unsere Funktionsinstanz trotzdem zurückfordern. Selbst mit reservierten Instanzen bleiben die Daten im Arbeitsspeicher skalierter Knoten isoliert.
Um langlebige Prozesse zustandslos zu machen, müssen wir daher vermeiden, Werte im Hauptprozess zu speichern, oder nur temporäre Variablen speichern. Dauerhafte Daten sollten auf dedizierte zustandsbehaftete Knoten wie Datenbanken ausgelagert werden.
Durch die Trennung der Datenspeicherung vom Hauptprozessknoten und die Sicherstellung, dass der Hauptprozess keine Daten behält, wird unsere Anwendung zustandslos. Wir speichern die Daten in einem separaten, zustandsbehafteten Datenbankknoten. Dieses Beispiel wird zu dem im vorherigen Artikel besprochenen langlebigen Serverless-Szenario, bei dem wir beim Start des Hauptprozesses eine Verbindung zur Datenbank herstellen und über untergeordnete Prozesse auf Daten zugreifen. Dieser Ansatz hat jedoch einen erheblichen Nachteil: Er erhöht die Kaltstartzeit direkt. Gibt es eine bessere Lösung?
Betrachten wir einen alternativen Ansatz zur Datenpersistenz. Warum müssen wir die Verbindung zur Datenbank selbst herstellen? Unsere CRUD-Operationen (Create, Read, Update, Delete) auf Daten bestehen im Wesentlichen darin, dass untergeordnete Prozesse die vom Hauptprozess aufgebaute TCP-Verbindung wiederverwenden, Datenbankanweisungen senden und Daten abrufen. Stellen Sie sich vor, wir könnten mit HTTP-Anfragen wie POST, DELETE, PUT und GET Anweisungen an die Datenbank senden. Wäre das nicht in der Lage, die Konzepte der Daten- und Serviceorchestrierung aus der vorherigen Lektion zu nutzen?
Was ist BaaS?
Tatsächlich führt uns all diese Vorarbeit zum heutigen Protagonisten: BaaSification. Die Daten-Interaktionsoperationen POST, DELETE, PUT und GET entsprechen den semantischen HTTP-Methoden von RESTful-APIs. Am Beispiel von MySQL: POST entspricht dem Befehl CREATE, DELETE dem Befehl DELETE, PUT dem Befehl UPDATE und GET dem Befehl SELECT. Diese semantische Eins-zu-Eins-Entsprechung ermöglicht es uns, MySQL-Operationen auf natürliche Weise in RESTful-API-Operationen zu übersetzen.
Traditionelle Datenbankansätze sind aufgrund der Wiederverwendung von TCP-Verbindungen und des geringen Kommunikationsaufwands für dieselben Operationen im Vergleich zu HTTP schneller. Während Serverless direkt eine Verbindung zu Datenbanken herstellen kann, erweist sich die Verbindung zu herkömmlichen Datenbanken mithilfe von IP-Adressen in Cloud-Umgebungen mit VPC-Segmentierung oft als schwierig. Daher verlassen wir uns bei Serverless-Datenbankverbindungen in der Regel auf BaaS-Dienste von Cloud-Anbietern, obwohl viele BaaS-Dienste noch nicht ausgereift sind.
Wenn wir einen Schritt weiter gehen: Wenn Serverless nicht für zustandsbehaftete Knoten geeignet ist, warum nicht alle zustandsbehafteten Operationen als Datenschnittstellen externalisieren? Dies ermöglicht es unseren Serverless-Funktionen, den im vorherigen Artikel besprochenen Datenorchestrierungsansatz zu nutzen und eine freie Skalierung zu erreichen.
Zusammenfassung
Der Grund, warum das Run-to-Completion-Modell als reiner angesehen wird als das Modell langlebiger Prozesse, liegt darin, dass letzteres irreführend sein kann und uns dazu verleitet, es wie PaaS zu behandeln und als zustandsbehafteten Knoten für die permanente Datenspeicherung zu verwenden. Bei Serverless können Cloud-Anbieter jedoch selbst bei langlebigen Prozessen unsere Funktionsinstanzen zurückfordern.
Genau wie in unserem Beispiel, in dem das Speichern von Daten im Arbeitsspeicher bei jedem Neustart zu Rückstellungen führte, können wir durch die Übernahme einer Datenorchestrierungs-Denkweise und die Umwandlung von Backend-Datenbankoperationen in Datenschnittstellen die Datenspeicherung in Serverless an Backend-Anwendungen auslagern und mit ihnen interagieren, wie im vorherigen Artikel erläutert. Wir müssen jedoch über die reine Erstellung von Datenschnittstellen für Backend-Anwendungen hinausgehen. Wir müssen BaaSification einführen, um Backend-Ingenieure während der Entwicklung von serverseitigen Betriebsbelangen zu befreien.
Für die Skalierung können wir zwischen vertikaler und horizontaler Skalierung wählen. Vertikale Skalierung konzentriert sich auf die Verbesserung der Leistung einer einzelnen Maschine, geht jedoch oft mit einem steilen Kostenanstieg einher, was sie zu einer vorsichtigen Wahl macht. Horizontale Skalierung bedeutet die Erhöhung der Anzahl der Maschinen, bietet einen gleichmäßigeren Kostenverlauf und ist unsere Standardskalierungsmethode.
Zustandsbehaftete Knoten speichern Daten, während zustandslose Knoten Daten verarbeiten, ohne sie zu speichern. Nur zustandslose Knoten können frei skaliert werden. Zustandsbehaftete Knoten, die für die Speicherung kritischer Daten verantwortlich sind, erfordern eine sorgfältige Handhabung. Wenn wir möchten, dass unsere Netzwerktopologieknoten frei skaliert werden können, müssen wir ihre Datenoperationen an dedizierte zustandsbehaftete Knoten externalisieren.
Wenn unsere Serverless-Funktionen auf zustandsbehaftete Knoten zugreifen, ist es vorzuziehen, dass diese Knoten Datenschnittstellen bereitstellen, anstatt sich ausschließlich auf Datenbankbefehle zu verlassen, da Datenbankverbindungen für Serverless-Funktionen zusätzlichen Overhead verursachen. Um die Entwicklung für Backend-Ingenieure zu vereinfachen, sollten wir außerdem eine BaaSification zustandsbehafteter Knoten anstreben. Wir werden in nachfolgenden Artikeln tiefer in die BaaSification eintauchen.
Novita AI ist die All-in-One-Cloud-Plattform, die Ihre KI-Ambitionen unterstützt. Integrierte APIs, Serverless, GPU-Instanz – die kostengünstigen Tools, die Sie brauchen. Vermeiden Sie Infrastruktur, starten Sie kostenlos und machen Sie Ihre KI-Vision zur Realität.
Empfohlene Lektüre
Die Revolution enthüllen: Die Welt des Serverless Computing erkunden
