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-Servern đŸ”„ Kaufen Sie zuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz, VPS VDS-Servern | ProHoster