Tarantool Cartridge: Zerkleinern des Lua-Backends in drei Zeilen



Bei Mail.ru Group haben wir Tarantool - dies ist ein solcher Anwendungsserver auf Lua, der auch eine Datenbank hat (oder umgekehrt?). Es ist schnell und cool, aber die Funktionen eines Servers sind immer noch nicht unbegrenzt. Die vertikale Skalierung ist auch kein Allheilmittel, daher verfügt Tarantool über Werkzeuge für die horizontale Skalierung - das vshard-Modul [1] . Es ermöglicht Ihnen, Daten auf mehreren Servern zu teilen, aber Sie müssen daran basteln, um sie zu konfigurieren und die Geschäftslogik zu befestigen.

Gute Nachricht: Wir haben die Kegel gesammelt (zum Beispiel [2] , [3] ) und ein anderes Framework abgesägt, das die Lösung dieses Problems erheblich vereinfacht.

Tarantool Cartridge ist ein neues Framework für die Entwicklung komplexer verteilter Systeme. Sie können sich darauf konzentrieren, Geschäftslogik zu schreiben, anstatt Infrastrukturprobleme zu lösen. Unter dem Strich werde ich Ihnen sagen, wie dieses Framework organisiert ist und wie verteilte Dienste damit geschrieben werden.

Und was ist eigentlich das Problem?


Wir haben eine Vogelspinne, es gibt Vshard - was willst du mehr?

Erstens geht es um Bequemlichkeit. Die Vshard-Konfiguration wird über Lua-Tabellen konfiguriert. Damit ein verteiltes System mit mehreren Tarantool-Prozessen ordnungsgemäß funktioniert, muss die Konfiguration überall gleich sein. Niemand möchte dies manuell tun. Daher werden alle Arten von Skripten, Ansible und Bereitstellungssysteme verwendet.

Cartridge selbst verwaltet die vshard-Konfiguration, basierend auf seiner eigenen verteilten Konfiguration . Im Wesentlichen handelt es sich hierbei um eine einfache YAML-Datei, deren Kopie in jeder Instanz von Tarantool gespeichert ist. Die Vereinfachung liegt in der Tatsache, dass das Framework selbst seine Konfiguration überwacht und somit überall gleich ist.

Zweitens ist der Punkt wieder bequem. Die Konfiguration hat keinen Bezug zur Entwicklung der Geschäftslogik und lenkt den Programmierer nur von der Arbeit ab. Wenn wir die Architektur eines Projekts diskutieren, sprechen wir meistens über einzelne Komponenten und deren Interaktion. Es ist noch zu früh, um über die Einführung eines Clusters in 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 Rollenkonzept für jeden Tarantool-Prozess ein. Rollen sind ein Konzept, mit dem sich der Entwickler auf das Schreiben von Code konzentrieren kann. Alle im Projekt verfügbaren Rollen können auf einer Instanz von Tarantool ausgeführt werden. Dies reicht für Tests aus.

Hauptmerkmale der Tarantool-Patrone:

  • automatisierte Cluster-Orchestrierung;
  • Erweiterung der Anwendungsfunktionalität um neue Rollen;
  • Anwendungsentwicklungs- und Bereitstellungsvorlage;
  • eingebautes automatisches Sharding;
  • Integration in das Luatest-Testframework;
  • Clusterverwaltung über WebUI und API;
  • Verpackungs- und Bereitstellungstools.

Hallo Welt!


Ich bin gespannt darauf, das Framework selbst zu zeigen. Lassen wir also die Geschichte über Architektur für später und beginnen mit einer einfachen. Vorausgesetzt, Tarantool selbst ist bereits installiert, bleibt nur noch etwas zu tun

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

Mit diesen beiden Befehlen werden die Befehlszeilenprogramme installiert und Sie können Ihre erste Anwendung aus der Vorlage erstellen:

 $ 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 dem fertigen "Hallo Welt!" Anwendung. Versuchen wir sofort, es auszuführen und die Abhängigkeiten (einschließlich des Frameworks selbst) vorinstallieren:

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

Daher haben wir einen Knoten der zukünftigen Sharded-Anwendung gestartet. Ein neugieriger Laie kann sofort die Weboberfläche öffnen, mit der Maus einen Cluster von einem Knoten aus konfigurieren und das Ergebnis genießen, aber es ist zu früh, um sich zu freuen. Bisher weiß die Anwendung nicht, wie sie etwas Nützliches tun soll. Daher werde ich Sie später über die Bereitstellung informieren. Jetzt ist es an der Zeit, Code zu schreiben.

Anwendungsentwicklung


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



Wir beginnen ein Diagramm zu zeichnen und platzieren drei Komponenten darauf: Gateway, Speicher und Scheduler. Wir arbeiten weiter an der Architektur. Da wir vshard als Speicher verwenden, fügen wir dem Schema vshard-router und vshard-storage hinzu. Weder das Gateway noch der Scheduler greifen direkt auf das Repository zu. Dafür gibt es einen Router, der dafür erstellt wurde.



Dieses Schema spiegelt immer noch nicht ganz genau wider, was wir im Projekt erstellen werden, da die Komponenten abstrakt aussehen. Wir müssen auch sehen, wie dies auf ein echtes Tarantool projiziert wird - wir werden unsere Komponenten nach Prozessen gruppieren.



Es macht wenig Sinn, vshard-router und gateway auf getrennten Instanzen zu belassen. Warum müssen wir noch einmal über das Netzwerk gehen, wenn dies bereits in der Verantwortung des Routers liegt? Sie müssen im selben Prozess ausgeführt werden. Das heißt, in einem Prozess werden sowohl das Gateway als auch vshard.router.cfg initialisiert und können lokal interagieren.

Es war praktisch, in der Entwurfsphase mit drei Komponenten zu arbeiten, aber als Entwickler möchte ich beim Schreiben von Code nicht daran denken, drei Instanzen von Tarnatool zu starten. Ich muss Tests ausführen und überprüfen, ob ich das Gateway richtig geschrieben habe. Oder vielleicht möchte ich meinen Kollegen eine Funktion demonstrieren. Warum sollte ich unter der Bereitstellung von drei Kopien leiden? So wurde das Konzept der Rollen geboren. Eine Rolle ist ein reguläres Loach-Modul, dessen Lebenszyklus von Cartridge verwaltet wird. In diesem Beispiel gibt es vier davon: Gateway, Router, Speicher, Scheduler. In einem anderen Projekt kann es mehr geben. Alle Rollen können in einem Prozess gestartet werden, und dies wird ausreichen.



Und wenn es um die Bereitstellung für das Staging oder den Betrieb geht, werden wir jedem Tarantool-Prozess jeden Rollensatz zuweisen, abhängig von den Hardwarefunktionen:



Topologieverwaltung


Informationen darüber, wo welche Rollen gestartet werden, müssen irgendwo gespeichert werden. Und dieses "irgendwo" ist die verteilte Konfiguration, die ich oben erwähnt habe. Das Wichtigste dabei ist die Clustertopologie. Hier sind 3 Replikationsgruppen von 5 Tarantool-Prozessen:



Wir möchten keine Daten verlieren, daher behandeln wir Informationen über laufende Prozesse sorgfältig. Cartridge überwacht die Konfiguration mit einem zweiphasigen Commit. Sobald wir die Konfiguration aktualisieren möchten, überprüft sie zunächst die Verfügbarkeit aller Instanzen und ihre Bereitschaft, die neue Konfiguration zu akzeptieren. Danach wendet die zweite Phase die Konfiguration an. Selbst wenn eine Instanz vorübergehend nicht verfügbar war, wird nichts Schreckliches passieren. Die Konfiguration wird einfach nicht angewendet und Sie werden im Voraus einen Fehler sehen.

Auch im Topologieabschnitt wird ein so wichtiger Parameter wie der Leiter jeder Replikationsgruppe angegeben. Normalerweise ist dies die Instanz, die aufgezeichnet wird. Der Rest ist meistens schreibgeschützt, obwohl es Ausnahmen geben kann. Manchmal haben mutige Entwickler keine Angst vor Konflikten und können Daten parallel auf mehrere Replikate schreiben, aber es gibt einige Vorgänge, die trotz allem nicht zweimal ausgeführt werden sollten. Dafür gibt es ein Zeichen eines Führers.



Rollenleben


Damit eine abstrakte Rolle in einer solchen Architektur existiert, muss das Framework sie irgendwie verwalten. Die Steuerung erfolgt natürlich ohne Neustart des Tarantool-Prozesses. Es gibt 4 Rückrufe zum Verwalten von Rollen. Cartridge selbst ruft sie auf, je nachdem, was in einer verteilten Konfiguration angegeben ist, und wendet die Konfiguration dann auf bestimmte 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. Dort ist es beispielsweise praktisch, box.space.create zu initialisieren, oder der Scheduler kann eine Hintergrundfaser starten, die die Arbeit in bestimmten Intervallen erledigt.

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

Der Cluster ruft apply_config jeder Änderung der verteilten Konfiguration validate_config und apply_config . Wenn eine Konfiguration durch ein zweiphasiges Commit angewendet wird, überprüft der Cluster, ob jede Rolle bereit ist, diese neue Konfiguration zu akzeptieren, und meldet dem Benutzer gegebenenfalls einen Fehler. Wenn sich alle einig waren, dass die Konfiguration normal ist, wird apply_config .

Rollen haben auch eine stop , die erforderlich ist, um die Vitalfunktionen der Rolle zu löschen. Wenn wir sagen, dass der Scheduler auf diesem Server nicht mehr benötigt wird, kann er die Fasern stoppen, die mit init gestartet wurden.

Rollen können miteinander interagieren. Wir sind es gewohnt, Funktionsaufrufe in Lua zu schreiben, aber es kann vorkommen, dass wir nicht die Rolle haben, die wir in diesem Prozess benötigen. Um den Netzwerkzugriff zu erleichtern, verwenden wir das Hilfsmodul rpc (Remote Procedure Call), das auf der in Tarantool integrierten Standard-Netbox basiert. Dies kann nützlich sein, wenn Ihr Gateway beispielsweise den Scheduler direkt bitten möchte, die Aufgabe sofort zu erledigen, anstatt einen Tag zu warten.

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 - jeder Prozess teilt seinen Nachbarn die neuesten Nachrichten mit und sie reagieren. Wenn die Antwort nicht kommt, beginnt Tarantool zu vermuten, dass etwas nicht stimmt, und nach einer Weile rezitiert er den Tod und beginnt, allen von diesen Neuigkeiten zu erzählen.



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 selbst übernehmen, und Cartridge konfiguriert die ausgeführten Rollen entsprechend.



Hier müssen Sie vorsichtig sein, da ein häufiges Hin- und Herwechseln zu Datenkonflikten während der Replikation führen kann. Ein zufälliges automatisches Failover lohnt sich natürlich nicht. Sie müssen klar verstehen, was passiert, und sicherstellen, dass die Replikation nicht unterbrochen wird, nachdem sich der Anführer erholt hat und die Krone an ihn zurückgegeben wird.

Nach allem, was gesagt wurde, scheint es, dass die Rollen ähnlich wie bei Microservices sind. In gewissem Sinne sind sie nur als Module innerhalb von Tarantool-Prozessen. Es gibt jedoch eine Reihe grundlegender Unterschiede. Erstens müssen alle Projektrollen in einer Codebasis leben. Alle Tarantool-Prozesse sollten von einer Codebasis aus gestartet werden, damit es keine Überraschungen gibt, wenn wir versuchen, den Scheduler zu initialisieren, aber dies ist einfach nicht der Fall. Lassen Sie auch keine Unterschiede in den Versionen des Codes zu, da das Verhalten des Systems in einer solchen Situation sehr schwer vorherzusagen und zu debuggen ist.

Im Gegensatz zu Docker können wir das „Image“ einer Rolle nicht einfach auf einen anderen Computer übertragen und dort ausführen. Unsere Rollen sind nicht so isoliert wie Docker-Container. Außerdem können wir nicht zwei identische Rollen auf derselben Instanz ausführen. Die Rolle ist entweder da oder nicht, in gewissem Sinne ist es Singleton. Und drittens sollten die Rollen innerhalb der gesamten Replikationsgruppe gleich sein, da dies sonst lächerlich wäre - die Daten sind gleich und die Konfiguration ist unterschiedlich.

Bereitstellungstools


Ich habe versprochen zu zeigen, wie Cartridge bei der Bereitstellung von Apps hilft. Um anderen das Leben zu erleichtern, enthält das Framework RPM-Pakete:

 $ cartridge pack rpm myapp #    ./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 Lauch-Abhängigkeiten. Tarantool wird auch als RPM-Paketabhängigkeit auf den Server übertragen, und unser Service ist startbereit. Dies geschieht über systemd, aber zuerst müssen Sie eine kleine Konfiguration schreiben. Geben Sie mindestens den URI jedes Prozesses an. Drei zum Beispiel sind genug.

 $ 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 

Hier gibt es eine interessante Nuance. Anstatt nur den binären Protokollport anzugeben, geben wir die öffentliche Adresse des gesamten Prozesses einschließlich des Hostnamens an. Dies ist erforderlich, damit die Clusterknoten wissen, wie sie sich miteinander verbinden können. Es ist eine schlechte Idee, die Adresse 0.0.0.0 als Advertise_uri zu verwenden. Es sollte sich um eine externe IP-Adresse handeln, nicht um einen Bind-Socket. Ohne sie funktioniert nichts, sodass Cartridge den Knoten mit der falschen Advertise_uri einfach nicht starten lässt.

Nachdem die Konfiguration fertig ist, können Sie die Prozesse starten. Da eine normale systemd-Einheit nicht das Starten von mehr als einem Prozess zulässt, installieren Anwendungen auf Cartridge den sogenannten instanziierte Einheiten, die so funktionieren:

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

In der Konfiguration haben wir den HTTP-Port angegeben, an dem Cartridge die Webschnittstelle bedient - 8080. Lassen Sie uns das durchgehen und sehen:



Wir sehen, dass die Prozesse, obwohl sie ausgeführt werden, noch nicht konfiguriert sind. Die Kassette weiß noch nicht, wer mit wem replizieren soll, und kann nicht selbst entscheiden. Daher wartet sie auf unsere Aktion. Und unsere Wahl ist nicht groß: Die Lebensdauer eines neuen Clusters beginnt mit der Konfiguration des ersten Knotens. Anschließend fügen wir den Rest dem Cluster hinzu, weisen ihnen Rollen zu und können bei dieser Bereitstellung als erfolgreich abgeschlossen gelten.

Gießen Sie ein Glas Ihres Lieblingsgetränks ein und entspannen Sie sich nach einer langen Arbeitswoche. Die Anwendung kann ausgenutzt werden.



Zusammenfassung


Und was sind die Ergebnisse? Versuchen Sie, verwenden Sie, hinterlassen Sie Feedback, starten Sie Tickets auf dem Github.

Referenzen


[1] Tarantool »2.2» Referenz »Rocks Referenz» Modul vshard

[2] Wie wir den Kern des Investmentgeschäfts der Alfa-Bank basierend auf Tarantool implementiert haben

[3] Abrechnungsarchitektur der nächsten Generation: Übergang zu Tarantool

[4] SWIM - Clusterbuilding-Protokoll

[5] GitHub - tarantool / cartridge-cli

[6] GitHub - Tarantool / Cartridge

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


All Articles