Tarantool Cartridge: Sharding Lua Backend in drei Zeilen


In der Mail.ru-Gruppe haben wir Tarantool, einen Lua-basierten Anwendungsserver und eine Datenbank vereint. Es ist schnell und elegant, aber die Ressourcen eines einzelnen Servers sind immer begrenzt. Vertikale Skalierung ist auch kein Allheilmittel. Aus diesem Grund verfügt Tarantool über einige Tools für die horizontale Skalierung oder das vshard-Modul [1] . Sie können Daten auf mehrere Server verteilen, müssen jedoch eine Weile daran basteln, um sie zu konfigurieren und die Geschäftslogik zu verbessern.

Gute Nachricht: Wir haben unseren Anteil an Unebenheiten (z. B. [2] , [3] ) und ein weiteres Framework erstellt, das die Lösung dieses Problems erheblich vereinfacht.

Tarantool Cartridge ist das neue Framework für die Entwicklung komplexer verteilter Systeme. Sie können sich darauf konzentrieren, Geschäftslogik zu schreiben, anstatt Infrastrukturprobleme zu lösen. Im Folgenden werde ich Ihnen erläutern, wie dieses Framework funktioniert und wie es beim Schreiben verteilter Dienste hilfreich sein kann.

Was genau ist das Problem?


Wir haben Tarantool und vshard - was wollen wir mehr?

Erstens ist es eine Frage der Bequemlichkeit. Vshard ist in Lua-Tabellen konfiguriert. Damit ein verteiltes System mit mehreren Tarantool-Prozessen ordnungsgemäß funktioniert, muss die Konfiguration jedoch überall gleich sein. Niemand möchte dies manuell tun, daher werden alle Arten von Skripten, Ansible und Bereitstellungssystemen verwendet.

Cartridge selbst verwaltet die vshard-Konfiguration basierend auf seiner eigenen verteilten Konfiguration . Tatsächlich handelt es sich um eine einfache YAML-Datei, deren Kopie auf jeder Tarantool-Instanz gespeichert ist. Mit anderen Worten, das Framework überwacht seine Konfiguration so, dass sie überall gleich ist.

Zweitens ist es wieder eine Frage der Bequemlichkeit. Die Vshard-Konfiguration bezieht sich nicht auf die Entwicklung der Geschäftslogik und lenkt einen Entwickler nur von seiner Arbeit ab. Wenn wir die Architektur eines Projekts diskutieren, handelt es sich höchstwahrscheinlich um separate Komponenten und deren Interaktion. Es ist noch zu früh, um überhaupt über die Bereitstellung eines Clusters für drei Rechenzentren nachzudenken.

Wir haben diese Probleme immer wieder gelöst und irgendwann einen Ansatz entwickelt, um die Arbeit mit der Anwendung über den gesamten Lebenszyklus hinweg zu vereinfachen: Erstellung, Entwicklung, Test, CI / CD, Wartung.

Cartridge führt das Konzept der Rollen für jeden Tarantool-Prozess ein. Mithilfe von Rollen kann sich der Entwickler auf das Schreiben von Code konzentrieren. Alle im Projekt verfügbaren Rollen können auf der einzelnen Instanz von Tarantool ausgeführt werden. Dies würde zum Testen ausreichen.

Hauptmerkmale der Tarantool-Patrone:

  • automatisierte Cluster-Orchestrierung;
  • erweiterte Anwendungsfunktionalität mit neuen Rollen;
  • Anwendungsvorlage für Entwicklung und Bereitstellung;
  • eingebautes automatisches Sharding;
  • Integration in das Luatest-Framework;
  • Clusterverwaltung über WebUI und API;
  • Verpackungs- und Bereitstellungstools.

Hallo Welt!


Ich kann es kaum erwarten, Ihnen das Framework selbst zu zeigen. Speichern wir also die Geschichte über Architektur für später und beginnen Sie mit einer einfachen Aufgabe. Vorausgesetzt, Tarantool ist bereits installiert, müssen wir nur noch etwas tun

$ tarantoolctl rocks install cartridge-cli $ export PATH=$PWD/.rocks/bin/:$PATH 

Als Ergebnis werden die Befehlszeilenprogramme installiert, mit denen Sie Ihre erste Anwendung aus der Vorlage erstellen können:

 $ cartridge create --name myapp 

Und hier ist was wir bekommen:

 myapp/ ├── .git/ ├── .gitignore ├── app/roles/custom.lua ├── deps.sh ├── init.lua ├── myapp-scm-1.rockspec ├── test │ ├── helper │ │ ├── integration.lua │ │ └── unit.lua │ ├── helper.lua │ ├── integration/api_test.lua │ └── unit/sample_test.lua └── tmp/ 

Dies ist ein Git-Repository mit einem gebrauchsfertigen "Hallo Welt!" Anwendung. Versuchen wir, es nach der Installation der Abhängigkeiten (einschließlich des Frameworks selbst) auszuführen:

 $ tarantoolctl rocks make $ ./init.lua --http-port 8080 

Wir haben einen Knoten unserer zukünftigen Sharded-Anwendung gestartet. Wenn Sie neugierig sind, können Sie sofort die Weboberfläche öffnen, die auf localhost : 8080 ausgeführt wird. Verwenden Sie eine Maus, um einen Cluster mit einem Knoten zu konfigurieren und das Ergebnis zu genießen, aber freuen Sie sich nicht zu früh. Die Anwendung weiß noch nicht, wie sie etwas Nützliches tun soll, daher werde ich Sie später über die Bereitstellung informieren, und jetzt ist es Zeit, Code zu schreiben.

Anwendungen entwickeln


Stellen Sie sich vor, wir entwerfen ein System, das Daten empfangen, speichern und einmal täglich einen Bericht erstellen soll.


Wir zeichnen also ein Diagramm mit drei Komponenten: Gateway, Speicher und Scheduler. Lassen Sie uns weiter an der Architektur arbeiten. Da wir vshard als Speicher verwenden, fügen wir dem Diagramm vshard-router und vshard-storage hinzu. Weder das Gateway noch der Scheduler greifen direkt auf den Speicher zu - für diese Aufgabe wird explizit ein Router erstellt.


Dieses Diagramm sieht abstrakt aus, da die Komponenten immer noch nicht das widerspiegeln, was wir im Projekt erstellen werden. Wir müssen sehen, wie dieses Projekt echtem Tarantool entspricht, also gruppieren wir unsere Komponenten nach dem Prozess.


Es macht wenig Sinn, vshard-router und gateway auf getrennten Instanzen zu halten. Warum sollten wir noch einmal über das Netzwerk gehen, wenn dies bereits in der Verantwortung des Routers liegt? Sie sollten innerhalb desselben Prozesses ausgeführt werden, dh sowohl das Gateway als auch vshard.router.cfg sollten im selben Prozess initialisiert werden und lokal interagieren.

Während der Entwurfsphase war es praktisch, mit drei Komponenten zu arbeiten, aber als Entwickler möchte ich nicht daran denken, drei Instanzen von Tarantool beim Schreiben von Code zu starten. Ich muss die Tests ausführen und überprüfen, ob ich den Gateway-Code korrekt geschrieben habe. Oder ich möchte meinen Mitarbeitern eine neue Funktion zeigen. Warum sollte ich Probleme mit der Bereitstellung von drei Instanzen haben? So wurde das Konzept der Rollen geboren. Eine Rolle ist ein reguläres Lua-Modul, und Cartridge verwaltet seinen Lebenszyklus. In diesem Beispiel gibt es vier davon: Gateway, Router, Speicher und Scheduler. Ein anderes Projekt hat möglicherweise mehr Rollen. Alle Rollen können in einem Prozess gestartet werden, und es würde ausreichen.


Wenn es um die Bereitstellung für Staging oder Produktion geht, weisen wir jedem Tarantool-Prozess abhängig von den zugrunde liegenden Hardwarefunktionen einen separaten Rollensatz zu:


Topologieverwaltung


Wir sollten auch irgendwo Informationen über die laufenden Rollen speichern. Und "irgendwo" bedeutet die oben erwähnte verteilte Konfiguration. Das Wichtigste dabei ist die Clustertopologie. Hier sehen Sie 3 Replikationsgruppen von 5 Tarantool-Prozessen:


Wir möchten die Daten nicht verlieren, deshalb behandeln wir die Informationen über die laufenden Prozesse mit Sorgfalt. Cartridge überwacht die Konfiguration mithilfe eines zweiphasigen Commits. Sobald wir die Konfiguration aktualisieren möchten, wird zunächst geprüft, ob die Instanzen verfügbar und bereit sind, die neue Konfiguration zu akzeptieren. Danach wird die Konfiguration in der zweiten Phase angewendet. Selbst wenn eine Instanz vorübergehend nicht verfügbar ist, kann nichts schief gehen. Die Konfiguration wird einfach nicht angewendet und Sie werden im Voraus einen Fehler sehen.

Der Topologieabschnitt enthält auch einen so wichtigen Parameter wie den Leiter jeder Replikationsgruppe. Normalerweise ist dies die Instanz, die die Schreibvorgänge akzeptiert. Der Rest ist meistens schreibgeschützt, obwohl es Ausnahmen geben kann. Manchmal haben mutige Entwickler keine Angst vor Konflikten und können Daten gleichzeitig auf mehrere Replikate schreiben. Einige Operationen sollten jedoch nicht zweimal ausgeführt werden. Deshalb haben wir einen Führer.


Rollenlebenszyklus


Damit eine Projektarchitektur abstrakte Rollen enthält, muss das Framework diese irgendwie verwalten können. Natürlich werden die Rollen verwaltet, ohne den Tarantool-Prozess neu zu starten. Es gibt vier Rückrufe für das Rollenmanagement. Cartridge selbst ruft sie abhängig von den Informationen aus der verteilten Konfiguration auf und wendet dabei die Konfiguration auf die spezifischen Rollen an.

 function init() function validate_config() function apply_config() function stop() 

Jede Rolle hat eine init Funktion. Es wird einmal aufgerufen: entweder wenn die Rolle aktiviert ist oder wenn Tarantool neu gestartet wird. Hier ist es beispielsweise praktisch, box.space.create zu initialisieren, oder der Scheduler kann eine Hintergrundfaser ausführen, die die Aufgabe in regelmäßigen Abständen erledigt.

Die init Funktion allein reicht möglicherweise nicht aus. Mit Cartridge können die Rollen auf die verteilte Konfiguration zugreifen, die zum Speichern der Topologie verwendet wird. In derselben Konfiguration können wir einen neuen Abschnitt deklarieren und dort einen Teil der Geschäftskonfiguration speichern. In meinem Beispiel kann dies ein Datenschema oder Zeitplaneinstellungen für die Scheduler-Rolle sein.

Der Cluster ruft jedes Mal validate_config und apply_config auf, wenn sich die verteilte Konfiguration ändert. Wenn eine Konfiguration in einem zweiphasigen Commit angewendet wird, überprüft der Cluster, ob jede Rolle auf jedem Server bereit ist, diese neue Konfiguration zu akzeptieren, und meldet dem Benutzer gegebenenfalls einen Fehler. Wenn alle mit der Konfiguration einverstanden sind, wird apply_config aufgerufen.

Rollen unterstützen auch eine stop zum Reinigen des Mülls. Wenn wir sagen, dass der Scheduler auf diesem Server nicht benötigt wird, kann er die Fasern stoppen, die er mit init gestartet hat.

Rollen können miteinander interagieren. Wir sind es gewohnt, Lua-Funktionsaufrufe zu schreiben, aber der Prozess hat möglicherweise nicht die erforderliche Rolle. Um den Netzwerkzugriff zu erleichtern, verwenden wir ein Hilfsmodul namens rpc (Remote Procedure Call), das auf dem Standardmodul Tarantool net.box basiert. Dies kann beispielsweise nützlich sein, wenn Ihr Gateway den Scheduler direkt anfordern möchte, die Aufgabe jetzt und nicht an einem Tag auszuführen.

Ein weiterer wichtiger Punkt ist die Gewährleistung der Fehlertoleranz. Cartridge verwendet das SWIM-Protokoll [4] , um den Zustand zu überwachen. Kurz gesagt, die Prozesse tauschen über UDP "Gerüchte" miteinander aus, dh jeder Prozess teilt seinen Nachbarn die neuesten Nachrichten mit und sie antworten. Wenn es plötzlich keine Antwort mehr gibt, vermutet Tarantool, dass etwas nicht stimmt, und nach einer Weile erklärt es den Tod und sendet diese Nachricht an alle.


Basierend auf diesem Protokoll organisiert Cartridge ein automatisches Failover. Jeder Prozess überwacht seine Umgebung. Wenn der Leiter plötzlich nicht mehr reagiert, kann das Replikat seine Rolle beanspruchen, und Cartridge konfiguriert die ausgeführten Rollen entsprechend.


Hier müssen Sie vorsichtig sein, da häufiges Hin- und Herwechseln zu Datenkonflikten während der Replikation führen kann. Das automatische Failover sollte auf keinen Fall zufällig aktiviert werden. Sie sollten eine klare Vorstellung davon haben, was vor sich geht, und sicherstellen, dass die Replikation nicht abstürzt, wenn der Anführer sich erholt und seine Krone wiedererlangt.

Nach allem, was gesagt wurde, scheinen die Rollen den Mikrodiensten ähnlich zu sein. In gewissem Sinne sind sie nur Module in Tarantool-Prozessen, und es gibt mehrere grundlegende Unterschiede. Erstens müssen alle Projektrollen in derselben Codebasis leben. Alle Tarantool-Prozesse sollten auf derselben Codebasis ausgeführt werden, damit es keine Überraschungen gibt, beispielsweise wenn wir versuchen, den Scheduler zu initialisieren, aber es gibt einfach keinen Scheduler. Außerdem sollten wir keine Unterschiede in den Codeversionen zulassen, da das Systemverhalten in einer solchen Situation kompliziert vorherzusagen und zu debuggen ist.

Im Gegensatz zu Docker können wir nicht einfach ein "Image" einer Rolle erstellen, es auf einen anderen Computer übertragen und dort ausführen. Unsere Rollen sind nicht so isoliert wie Docker-Container. Darüber hinaus können wir nicht zwei identische Rollen auf derselben Instanz ausführen. Die Rolle ist entweder da oder nicht; In gewissem Sinne ist es ein Singleton. Und drittens sollten die Rollen innerhalb der gesamten Replikationsgruppe gleich sein, da dies sonst lächerlich aussehen würde: Die Daten sind gleich, aber das Verhalten ist unterschiedlich.

Bereitstellungstools


Ich habe versprochen, Ihnen zu zeigen, wie Cartridge bei der Bereitstellung von Anwendungen helfen kann. Um das Leben einfacher zu machen, erstellt das Framework RPM-Pakete:

 $ cartridge pack rpm myapp # will create ./myapp-0.1.0-1.rpm $ sudo yum install ./myapp-0.1.0-1.rpm 

Das installierte Paket enthält fast alles, was Sie benötigen: sowohl die Anwendung als auch die installierten Lua-Abhängigkeiten. Tarantool kommt auch als RPM-Paketabhängigkeit auf den Server, und unser Service ist startbereit. Dies geschieht alles mit systemd, aber zuerst sollten wir eine Konfiguration vornehmen, zumindest den URI jedes Prozesses angeben. Drei wären genug für unser Beispiel.

 $ sudo tee /etc/tarantool/conf.d/demo.yml <<CONFIG myapp.router: {"advertise_uri": "localhost:3301", "http_port": 8080} myapp.storage_A: {"advertise_uri": "localhost:3302", "http_enabled": False} myapp.storage_B: {"advertise_uri": "localhost:3303", "http_enabled": False} CONFIG 

Es gibt einen interessanten Aspekt, der berücksichtigt werden sollte: Anstatt nur den binären Protokollport anzugeben, geben wir die öffentliche Adresse des gesamten Prozesses an, einschließlich des Hostnamens. Wir tun dies, weil die Clusterknoten wissen sollten, wie sie sich miteinander verbinden. Es wäre eine schlechte Idee, die 0.0.0.0-Adresse als Advertise_uri zu verwenden, da es sich um eine externe IP-Adresse und nicht um eine Socket-Bindung handeln sollte. Ohne funktioniert nichts, sodass Cartridge den Knoten mit der falschen Advertise_uri einfach nicht starten lässt.

Nachdem die Konfiguration fertig ist, können wir die Prozesse starten. Da eine normale systemd-Einheit nicht das Starten mehrerer Prozesse erlaubt, installieren die sogenannten instanziierten Einheiten die Anwendungen auf Cartridge:

 $ sudo systemctl start myapp@router $ sudo systemctl start myapp@storage_A $ sudo systemctl start myapp@storage_B 

Wir haben den HTTP-Port für die Cartridge-Weboberfläche in der Konfiguration angegeben: 8080. Gehen wir dort hin und schauen:


Wir können sehen, dass die Prozesse noch nicht konfiguriert sind, obwohl sie bereits ausgeführt werden. Cartridge weiß noch nicht, wie die Replikation durchgeführt werden soll, und kann nicht selbst entscheiden. Daher wartet es auf unsere Aktionen. Wir haben keine große Auswahl: Die Lebensdauer eines neuen Clusters beginnt mit der Konfiguration des ersten Knotens. Anschließend fügen wir dem Cluster weitere Knoten hinzu, weisen ihnen Rollen zu, und die Bereitstellung kann als erfolgreich abgeschlossen betrachtet werden.

Gießen wir uns einen Drink ein und entspannen uns nach einer langen Arbeitswoche. Die Anwendung ist einsatzbereit.


Ergebnisse


Was ist mit den Ergebnissen? Bitte testen, verwenden, Feedback geben und Tickets auf Github erstellen.

Referenzen


[1] Tarantool »2.2» Referenz »Rocks Referenz» Modul vshard
[2] Wie wir den Kern des auf Tarantool basierenden Investmentgeschäfts der Alfa-Bank implementiert haben
[3] Abrechnungsarchitektur der nächsten Generation: Übergang zu Tarantool
[4] SWIM - Cluster Building Protocol
[5] GitHub - tarantool / cartridge-cli
[6] GitHub - Tarantool / Cartridge

Source: https://habr.com/ru/post/de470812/


All Articles