Tarantool Cartridge: Sharding des Lua-Backends in drei Zeilen

Tarantool Cartridge: Sharding des Lua-Backends in drei Zeilen

In der Mail.ru Group haben wir Tarantool – einen Lua-Anwendungsserver, der gleichzeitig auch eine Datenbank ist (oder umgekehrt?). Er ist schnell und leistungsstark, aber die Möglichkeiten eines einzelnen Servers sind dennoch begrenzt. Vertikale Skalierung ist auch keine Allheilmethode, daher bietet Tarantool Werkzeuge für horizontale Skalierung – das vshard-Modul. [1]Es ermöglicht das Sharding von Daten über mehrere Server, jedoch muss man etwas Zeit investieren, um es einzurichten und die Geschäftslogik zu integrieren.

Gute Nachrichten: Wir haben einige Erfahrungen gesammelt (zum Beispiel [2], [3]) und ein neues Framework entwickelt, das die Lösung dieses Problems erheblich vereinfacht.

Tarantool Cartridge ist ein neues Framework zur Entwicklung komplexer verteilter Systeme. Es ermöglicht, sich auf das Schreiben von Geschäftslogik zu konzentrieren, anstatt Infrastrukturprobleme zu lösen. Im Folgenden erkläre ich, wie dieses Framework aufgebaut ist und wie man damit verteilte Dienste erstellt.

Aber was ist das eigentliche Problem?

Wir haben Tarantool, wir haben vshard – was könnte man sich mehr wünschen?

Zunächst einmal geht es um die Benutzerfreundlichkeit. Die vshard-Konfiguration wird über Lua-Tabellen eingestellt. Damit ein verteiltes System aus mehreren Tarantool-Prozessen korrekt funktioniert, muss die Konfiguration überall identisch sein. Niemand möchte dies manuell tun. Daher kommen verschiedene Skripte, Ansible und Bereitstellungssysteme zum Einsatz.

Cartridge verwaltet die vshard-Konfiguration selbst und basiert dabei auf seiner eigenen verteilten Konfiguration. Im Grunde handelt es sich dabei um eine einfache YAML-Datei, deren Kopie in jeder Tarantool-Instanz gespeichert wird. Die Vereinfachung besteht darin, dass das Framework selbst auf seine Konfiguration achtet und sicherstellt, dass sie überall gleich bleibt.

Zweitens geht es erneut um die Benutzerfreundlichkeit. Die vshard-Konfiguration hat nichts mit der Entwicklung der Geschäftslogik zu tun und lenkt den Programmierer nur von der Arbeit ab. Wenn wir die Architektur eines Projekts besprechen, geht es meistens um einzelne Komponenten und ihre Interaktion. An die Bereitstellung eines Clusters in drei Rechenzentren ist noch nicht zu denken.

Wir haben diese Herausforderungen immer wieder gelöst und es gelang uns, einen Ansatz zu entwickeln, der die Arbeit mit der Anwendung über ihren gesamten Lebenszyklus hinweg vereinfacht: von der Erstellung über die Entwicklung, das Testen, CI/CD bis zur Wartung.

Cartridge führt das Konzept von Rollen für jeden Prozess von Tarantool ein. Rollen sind das Konzept, das es Entwicklern ermöglicht, sich auf das Codieren zu konzentrieren. Alle im Projekt vorhandenen Rollen können auf einer einzigen Tarantool-Instanz ausgeführt werden, was für Tests ausreichend ist.

Die Hauptfunktionen von Tarantool Cartridge:

  • automatisierte Orchestrierung des Clusters;
  • Erweiterung der Anwendungsfunktionen durch neue Rollen;
  • Anwendungsvorlage für Entwicklung und Bereitstellung;
  • integriertes automatisches Sharding;
  • Integration mit dem Testframework Luatest;
  • Clusterverwaltung über WebUI und API;
  • Verpackungs- und Bereitstellungstools.

Hallo, Welt!

Ich kann es kaum erwarten, das Framework zu zeigen, daher lassen wir den Architekturteil für später und beginnen mit dem Einfachen. Unter der Annahme, dass Tarantool bereits installiert ist, müssen wir nur noch

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

Diese beiden Befehle installieren die Konsolenwerkzeuge und ermöglichen es Ihnen, Ihre erste Anwendung aus einer Vorlage zu erstellen:

$ cartridge create --name myapp

Und das erhalten wir:

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 einer fertigen „Hello, World!“-Anwendung. Lassen Sie uns sofort versuchen, sie zu starten, indem wir die Abhängigkeiten installieren (einschließlich des Frameworks):

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

So, wir haben einen Knoten der zukünftigen shardierten Anwendung gestartet. Neugierige Nutzer können sofort das Web-Interface öffnen, den Cluster aus einem Knoten per Maus konfigurieren und das Ergebnis genießen, aber es ist noch zu früh für Freude. Momentan kann die Anwendung nichts Nützliches tun, daher werde ich später über das Deployment sprechen; 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.

Tarantool Cartridge: Sharding des Lua-Backends in drei Zeilen

Wir beginnen mit der Visualisierung des Schemas und fügen drei Komponenten hinzu: Gateway, Storage und Scheduler. Wir entwickeln die Architektur weiter. Da wir vshard als Speicher verwenden, fügen wir dem Schema vshard-router und vshard-storage hinzu. Weder das Gateway noch der Scheduler werden direkt auf den Speicher zugreifen, dafür ist der Router zuständig.

Tarantool Cartridge: Sharding des Lua-Backends in drei Zeilen

Dieses Schema spiegelt noch nicht genau wider, was wir im Projekt erstellen werden, da die Komponenten abstrakt erscheinen. Wir müssen auch betrachten, wie sich dies auf das reale Tarantool abbildet—wir gruppieren unsere Komponenten nach Prozessen.

Tarantool Cartridge: Sharding des Lua-Backends in drei Zeilen

Es macht wenig Sinn, vshard-router und Gateway auf separaten Instanzen zu halten. Warum sollten wir die Netzwerkverbindung unnötig belasten, wenn das bereits Aufgabe des Routers ist? Sie sollten innerhalb eines Prozesses ausgeführt werden. Das heißt, sowohl Gateway als auch vshard.router.cfg werden in einem Prozess initialisiert und interagieren lokal.

In der Projektierungsphase war es praktisch, mit drei Komponenten zu arbeiten, aber als Entwickler möchte ich beim Programmieren nicht daran denken müssen, drei Instanzen von Tarnatool zu starten. Ich muss Tests durchführen und überprüfen, ob ich den Gateway korrekt implementiert habe. Oder vielleicht möchte ich meinen Kollegen eine Funktion demonstrieren. Warum sollte ich mich mit dem Deployment von drei Instanzen herumschlagen? So entstand das Konzept der Rollen. Eine Rolle ist ein gewöhnliches Lua-Modul, dessen Lebenszyklus von einem Cartridge verwaltet wird. In diesem Beispiel gibt es vier — Gateway, Router, Speicher, Scheduler. In einem anderen Projekt könnte es mehr geben. Alle Rollen können in einem Prozess gestartet werden, was ausreicht.

Tarantool Cartridge: Sharding des Lua-Backends in drei Zeilen

Wenn es um das Deployment in Staging oder den produktiven Einsatz geht, werden wir jedem Tarantool-Prozess sein eigenes Set von Rollen zuweisen, abhängig von den verfügbaren Hardware-Ressourcen:

Tarantool Cartridge: Sharding des Lua-Backends in drei Zeilen

Verwaltung der Topologie

Die Informationen darüber, wo welche Rollen ausgeführt werden, müssen irgendwo gespeichert werden. Und dieses „Irgendwo“ ist die verteilte Konfiguration, die ich oben bereits erwähnt habe. Das Wichtigste dabei ist die Topologie des Clusters. Hier sind 3 Replikationsgruppen mit insgesamt 5 Tarantool-Prozessen dargestellt:

Tarantool Cartridge: Sharding des Lua-Backends in drei Zeilen

Wir möchten keine Daten verlieren, daher gehen wir sorgsam mit den Informationen über die laufenden Prozesse um. Cartridge überwacht die Konfiguration mithilfe eines zweiphasigen Commits. Sobald wir die Konfiguration aktualisieren möchten, überprüft es zuerst die Verfügbarkeit aller Instanzen und deren Bereitschaft, die neue Konfiguration zu akzeptieren. In der zweiten Phase wird die Konfiguration angewendet. So passiert selbst dann nichts Schlimmes, wenn eine Instanz vorübergehend nicht verfügbar ist. Die Konfiguration wird einfach nicht angewendet, und Sie sehen im Voraus den Fehler.

In der Topologie-Sektion ist auch ein wichtiger Parameter angegeben: der Leader jeder Replikationsgruppe. In der Regel handelt es sich dabei um die Instanz, auf die geschrieben wird. Die anderen sind häufig nur für Leseoperationen vorgesehen, obwohl es hier Ausnahmen geben kann. Manchmal scheuen sich mutige Entwickler nicht vor Konflikten und können Daten parallel auf mehreren Replikaten schreiben, jedoch gibt es bestimmte Operationen, die auf keinen Fall zweimal durchgeführt werden sollten. Dafür gibt es das Merkmal des Leaders.

Tarantool Cartridge: Sharding des Lua-Backends in drei Zeilen

Rollenlebenszyklus

Damit eine abstrakte Rolle in einer solchen Architektur existieren kann, muss das Framework sie entsprechend verwalten. Natürlich erfolgt die Verwaltung ohne einen Neustart des Tarantool-Prozesses. Es gibt vier Callback-Funktionen zur Verwaltung der Rollen. Cartridge wird sie in Abhängigkeit von dem, was in der verteilten Konfiguration definiert ist, aufrufen und somit die Konfiguration auf die spezifischen Rollen anwenden.

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

Jede Rolle hat eine Funktion init. Diese wird einmalig entweder beim Aktivieren der Rolle oder beim Neustart von Tarantool aufgerufen. Dort ist es praktisch, beispielsweise box.space.create zu initialisieren, oder der Scheduler kann einen Hintergrund-Fiber starten, der Aufgaben in bestimmten Zeitintervallen ausführt.

Eine Funktion init kann nicht ausreichend sein. Cartridge ermöglicht es den Rollen, die verteilte Konfiguration zu nutzen, die zur Speicherung der Topologie verwendet wird. In dieser Konfiguration können wir einen neuen Abschnitt deklarieren und darin einen Teil der Geschäftskonfiguration aufbewahren. In meinem Beispiel könnte dies ein Datenschema oder die Einstellungen für den Scheduler-Rollen sein.

Der Cluster ruft validate_config und apply_config Bei jeder Änderung der verteilten Konfiguration wird überprüft, ob jede Rolle bereit ist, diese neue Konfiguration anzunehmen, wenn die Konfiguration mittels eines Zwei-Phasen-Commits angewendet wird. Im Falle von Problemen wird der Benutzer informiert. Wenn alle zustimmen, dass die Konfiguration in Ordnung ist, wird sie ausgeführt. apply_config.

Außerdem haben die Rollen eine Methode stop, die zur Bereinigung der Ergebnisse der Rollenaktivität benötigt wird. Wenn wir sagen, dass der Scheduler auf diesem Server nicht mehr benötigt wird, kann er die Fibers, die er gestartet hat, anhalten. init.

Rollen können miteinander interagieren. Wir sind es gewohnt, Funktionsaufrufe in Lua zu schreiben, aber es kann vorkommen, dass die benötigte Rolle in diesem Prozess nicht vorhanden ist. Um Netzwerkaufrufe zu erleichtern, verwenden wir das Hilfsmodul rpc (Remote Procedure Call), das auf dem standardmäßigen netbox basiert, der in Tarantool integriert ist. Dies ist nützlich, wenn zum Beispiel Ihr Gateway direkt den Scheduler auffordern möchte, eine Aufgabe sofort zu erledigen, anstatt einen Tag zu warten.

Ein weiterer wichtiger Punkt ist die Gewährleistung der Fehlertoleranz. Für die Gesundheitsüberwachung verwendet Cartridge das SWIM-Protokoll. [4]. Kurz gesagt, tauschen Prozesse „Gerüchte“ über UDP aus – jeder Prozess berichtet seinen Nachbarn die neuesten Nachrichten, und sie antworten. Wenn plötzlich keine Antwort kommt, beginnt Tarantool, etwas Verdächtiges zu vermuten, und nach einer Weile verkündet es den Tod des Prozesses und erzählt allen anderen von dieser Nachricht.

Tarantool Cartridge: Sharding des Lua-Backends in drei Zeilen

Basierend auf diesem Protokoll organisiert Cartridge die automatische Fehlermanagement. Jeder Prozess beobachtet seine Umgebung, und wenn der Leader plötzlich nicht mehr antwortet, kann eine Replikation seine Rolle übernehmen, während Cartridge die gestarteten Rollen entsprechend konfiguriert.

Tarantool Cartridge: Sharding des Lua-Backends in drei Zeilen

Hier ist Vorsicht geboten, denn häufiges Hin- und Herwechseln kann zu Datenkonflikten bei der Replikation führen. Automatischen Failover willkürlich zu aktivieren, sollte man natürlich vermeiden. Man muss genau verstehen, was passiert, und sicher sein, dass die Replikation nicht fehlschlägt, nachdem der Leader wiederhergestellt ist und ihm die Krone zurückgegeben wird.

Aus allem Gesagten könnte der Eindruck entstehen, dass Rollen ähnlich wie Mikrodienste sind. In gewissem Sinne sind sie das auch, jedoch als Module innerhalb der Prozesse von Tarantool. Es gibt jedoch auch einige wesentliche Unterschiede. Erstens sollten alle Projektrollen in einer einzigen Codebasis leben. Zudem müssen alle Tarantool-Prozesse aus dieser Codebasis gestartet werden, um Überraschungen zu vermeiden, wie beispielsweise wenn wir versuchen, den Scheduler zu initialisieren und dieser einfach nicht vorhanden ist. Ebenso sollten wir Unterschiede in den Codeversionen vermeiden, denn das Verhalten des Systems in solch einer Situation ist sehr schwer vorherzusagen und zu debuggen.

Im Gegensatz zu Docker können wir nicht einfach ein „Image“ der Rolle nehmen, es auf eine andere Maschine bringen und dort starten. Unsere Rollen sind nicht so isoliert wie Docker-Container. Zudem können wir auf einem Exemplar keine zwei identischen Rollen starten. Eine Rolle gibt es entweder, oder sie ist nicht vorhanden – in gewissem Sinne ist sie ein Singleton. Und drittens sollten innerhalb der gesamten Replikationsgruppe die Rollen identisch sein, denn ansonsten wäre es absurd – die Daten sind identisch, aber die Konfiguration ist unterschiedlich.

Deployment-Tools

Ich habe versprochen, zu zeigen, wie Cartridge bei der Bereitstellung von Anwendungen hilft. Um es den Nutzern zu erleichtern, verpackt das Framework RPM-Pakete:

$ cartridge pack rpm myapp -- wird für uns ./myapp-0.1.0-1.rpm verpacken
$ sudo yum install ./myapp-0.1.0-1.rpm

Das installierte Paket enthält fast alles, was nötig ist: sowohl die Anwendung als auch die installierten Lua-Abhängigkeiten. Tarantool wird ebenfalls als Abhängigkeit des RPM-Pakets auf den Server geliefert, und unser Service ist bereit zum Start. Dies geschieht über systemd, aber vorher muss ein wenig Konfiguration geschrieben werden. Mindestens die URI jedes Prozesses anzugeben, sind drei als Beispiel ausreichend.

$ 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 eine interessante Nuance. Anstatt nur den Port des binären Protokolls anzugeben, geben wir die öffentliche Adresse des Prozesses einschließlich des Hostnamens an. Das ist notwendig, damit die Knoten des Clusters wissen, wie sie sich miteinander verbinden können. Es ist keine gute Idee, die Adresse 0.0.0.0 als advertise_uri zu verwenden; dies sollte die externe IP-Adresse und nicht die Bind-Socket-Adresse sein. Ohne diese Verbindung wird nichts funktionieren, daher lässt Cartridge den Start eines Knotens mit einer falschen advertise_uri einfach nicht zu.

Jetzt, wo die Konfiguration bereit ist, können die Prozesse gestartet werden. Da ein reguläres systemd-Unit nicht erlaubt, mehr als einen Prozess zu starten, verwendet die Anwendung auf Cartridge sogenannte instantiierte Einheiten, die folgendermaßen 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, über den Cartridge die Web-Oberfläche bereitstellt – 8080. Lassen Sie uns darauf zugreifen und sehen, was wir finden:

Tarantool Cartridge: Sharding des Lua-Backends in drei Zeilen

Wir sehen, dass die Prozesse zwar gestartet sind, aber noch nicht konfiguriert wurden. Der Cartridge weiß bisher nicht, wer mit wem replizieren soll, und kann keine Entscheidungen selbstständig treffen, deshalb wartet er auf unsere Maßnahmen. Unsere Optionen sind begrenzt: Das Leben des neuen Clusters beginnt mit der Konfiguration des ersten Knotens. Danach fügen wir die anderen Knoten hinzu, weisen ihnen Rollen zu, und damit gilt das Deployment als erfolgreich abgeschlossen.

Lass uns ein Glas unseres Lieblingsgetränks nehmen und uns nach einer langen Arbeitswoche entspannen. Die Anwendung kann genutzt werden.

Tarantool Cartridge: Sharding des Lua-Backends in drei Zeilen

Ergebnisse

Und wie sind die Ergebnisse? Probiert es aus, nutzt es, hinterlasst euer Feedback und eröffnet Tickets auf GitHub.

Links

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

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

[3] Architektur des neuen Generation-Billing: Transformation mit dem Wechsel zu Tarantool

[4] SWIM — Protokoll zum Aufbau von Clustern

[5] GitHub — tarantool/cartridge-cli

[6] GitHub — tarantool/cartridge

Quelle: habr.com

Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server 🔥 Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster