
Bei Mail.ru Group haben wir Tarantool â einen App-Server in Lua, der gleichzeitig auch eine Datenbank ist (oder umgekehrt?). Er ist schnell und groĂartig, aber die Möglichkeiten eines einzelnen Servers sind dennoch nicht unbegrenzt. Vertikales Scaling ist auch nicht die Lösung aller Probleme, deshalb bietet Tarantool Werkzeuge fĂŒr horizontales Scaling â das Modul vshard. . Es ermöglicht, Daten auf mehreren Servern zu sharden, aber man muss sich die MĂŒhe machen, es einzurichten und die GeschĂ€ftslogik anzupassen.
Gute Nachrichten: Wir haben einige Fehler gemacht (zum Beispiel , ) und ein weiteres Framework entwickelt, das diese Probleme deutlich vereinfacht.
ist ein neues Framework fĂŒr die Entwicklung komplexer verteilter Systeme. Es ermöglicht, sich auf das Schreiben von GeschĂ€ftslogik zu konzentrieren, anstatt sich mit Infrastrukturproblemen auseinanderzusetzen. Im Folgenden werde ich erklĂ€ren, wie dieses Framework aufgebaut ist und wie man mit seiner Hilfe verteilte Dienste schreibt.
Worin besteht also das Problem?
Wir haben Tarantool, wir haben vshard â was könnte man sich mehr wĂŒnschen?
Erstens geht es um den Komfort. Die Konfiguration von vshard wird ĂŒber Lua-Tabellen eingestellt. Damit ein verteiltes System aus mehreren Tarantool-Prozessen korrekt funktioniert, muss die Konfiguration ĂŒberall gleich sein. Niemand möchte das manuell durchfĂŒhren. Deshalb kommen verschiedene Skripte, Ansible und Bereitstellungssysteme zum Einsatz.
Cartridge verwaltet selbst die vshard-Konfiguration, und das basierend auf seiner eigenen verteilten Konfiguration.Im Grunde genommen handelt es sich um eine einfache YAML-Datei, deren Kopie in jeder Tarantool-Instanz gespeichert ist. Die Vereinfachung besteht darin, dass das Framework selbst auf seine Konfiguration achtet und dafĂŒr sorgt, dass sie ĂŒberall gleich ist.
Zweitens geht es erneut um den Komfort. Die vshard-Konfiguration hat nichts mit der Entwicklung der GeschĂ€ftslogik zu tun und lenkt den Programmierer nur von seiner Arbeit ab. Wenn wir ĂŒber die Architektur eines Projekts sprechen, geht es oft um verschiedene Komponenten und deren Interaktion. Es ist noch zu frĂŒh, um ĂŒber das Deployment eines Clusters in 3 Rechenzentren nachzudenken.
Wir haben diese Probleme immer wieder angegangen, und irgendwann ist es uns gelungen, einen Ansatz zu entwickeln, der die Arbeit mit Anwendungen wÀhrend ihres gesamten Lebenszyklus vereinfacht: Erstellung, Entwicklung, Test, CI/CD, Wartung.
Das Cartridge-Framework fĂŒhrt das Konzept der Rollen fĂŒr jeden Prozess in Tarantool ein. Rollen sind das Konzept, das es Entwicklern ermöglicht, sich auf das Schreiben von Code zu konzentrieren. Alle vorhandenen Rollen im Projekt können auf einem Tarantool-Instanz gestartet werden, und dafĂŒr ist fĂŒr Tests ausreichend.
Die Hauptfunktionen von Tarantool Cartridge:
- automatisierte Orchestrierung des Clusters;
- Erweiterung der FunktionalitÀt der Anwendung mit neuen Rollen;
- Anwendungsvorlage fĂŒr Entwicklung und Bereitstellung;
- eingebautes automatisches Sharding;
- Integration mit dem Test-Framework Luatest;
- Clusterverwaltung ĂŒber WebUI und API;
- Pack- und Bereitstellungstools.
Hallo, Welt!
Ich kann es kaum erwarten, das Framework selbst zu zeigen, deshalb lassen wir die Architektur spĂ€ter hinter uns und beginnen mit etwas Einfachem. Angenommen, Tarantool ist bereits installiert, dann mĂŒssen wir nur noch
$ tarantoolctl rocks install cartridge-cli
$ export PATH=$PWD/.rocks/bin/:$PATHDiese beiden Befehle installieren die Kommandozeilen-Dienstprogramme und ermöglichen es, die erste Anwendung aus der Vorlage zu erstellen:
$ cartridge create --name myappUnd 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 bereitgestellten "Hello, World!"-Anwendung. Lass uns versuchen, es sofort zu starten, nachdem wir die AbhĂ€ngigkeiten installiert haben (einschlieĂlich des Frameworks):
$ tarantoolctl rocks make
$ ./init.lua --http-port 8080Jetzt lĂ€uft eine Node der zukĂŒnftigen Sharded-Anwendung. Der neugierige Betrachter kann sofort die WeboberflĂ€che öffnen, den Cluster mit einem Knoten mithilfe der Maus konfigurieren und das Ergebnis genieĂen, aber Freude ist momentan verfrĂŒht. Bis jetzt kann die Anwendung nichts NĂŒtzliches tun, also werde ich spĂ€ter ĂŒber das Deployment sprechen, und jetzt ist es Zeit, Code zu schreiben.
Anwendungsentwicklung
Stellen Sie sich vor, wir designen ein Projekt, das Daten empfangen, speichern und einmal tÀglich einen Bericht erstellen soll.

Wir beginnen, ein Schema zu zeichnen, und platzieren drei Komponenten darauf: gateway, storage und scheduler. Wir entwickeln die Architektur weiter. Da wir vshard als Speicher verwenden, fĂŒgen wir den vshard-router und den vshard-storage in das Schema ein. Weder gateway noch scheduler werden direkt auf den Speicher zugreifen, dafĂŒr gibt es den Router, der genau dafĂŒr geschaffen wurde.

Dieses Schema spiegelt immer noch nicht ganz genau wider, was wir im Projekt erstellen werden, da die Komponenten abstrakt erscheinen. Wir mĂŒssen noch prĂŒfen, wie sich das auf das echte Tarantool projiziert â lass uns unsere Komponenten nach Prozessen gruppieren.

Es macht wenig Sinn, vshard-router und gateway auf separaten Instanzen zu betreiben. Warum sollten wir unnötig durch das Netzwerk gehen, wenn das ohnehin in die Verantwortung des Routers fĂ€llt? Sie sollten innerhalb eines Prozesses gestartet werden. Das heiĂt, sowohl gateway als auch vshard.router.cfg werden in einem Prozess initialisiert, und sie sollten lokal miteinander interagieren.
In der Entwurfsphase war es praktisch, mit drei Komponenten zu arbeiten, aber ich als Entwickler möchte beim Schreiben des Codes nicht daran denken mĂŒssen, drei Instanzen von Tarantool zu starten. Ich muss Tests durchfĂŒhren und ĂŒberprĂŒfen, dass ich das gateway richtig implementiert habe. Oder vielleicht möchte ich eine Funktion meinen Kollegen vorfĂŒhren. Warum sollte ich mich mit dem Bereitstellen von drei Instanzen herumĂ€rgern? So entstand das Konzept der Rollen. Eine Rolle ist ein gewöhnliches Lua-Modul, dessen Lebenszyklus von Cartridge verwaltet wird. In diesem Beispiel gibt es vier Rollen: gateway, router, storage, scheduler. In einem anderen Projekt kann es mehr geben. Alle Rollen können in einem Prozess gestartet werden, und das wird ausreichen.

Wenn es um die Bereitstellung im Staging oder in der Produktion geht, werden wir jedem Tarantool-Prozess sein eigenes Set an Rollen je nach HardwarefÀhigkeiten zuweisen:

Management 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 daran ist die Topologie des Clusters. Hier sind 3 Replikationsgruppen aus 5 Tarantool-Prozessen dargestellt:

Wir wollen keine Daten verlieren, daher behandeln wir die Informationen ĂŒber die laufenden Prozesse sorgfĂ€ltig. Cartridge ĂŒberwacht die Konfiguration mithilfe eines zweiphasigen Commits. Sobald wir die Konfiguration aktualisieren möchten, ĂŒberprĂŒft es zunĂ€chst die VerfĂŒgbarkeit aller Instanzen und deren Bereitschaft, die neue Konfiguration zu akzeptieren. Danach wird in der zweiten Phase die Konfiguration angewendet. So passiert nichts Schlimmes, selbst wenn eine Instanz vorĂŒbergehend nicht verfĂŒgbar ist. Die Konfiguration wird einfach nicht angewendet und Sie werden im Voraus einen Fehler sehen.
In der Topologie-Sektion ist auch ein wichtiger Parameter angegeben, nĂ€mlich der Leader jeder Replikationsgruppe. Normalerweise handelt es sich dabei um die Instanz, auf die geschrieben wird. Die anderen sind meist read-only, obwohl es hier Ausnahmen geben kann. Manchmal scheuen mutige Entwickler keine Konflikte und können Daten parallel auf mehrere Replikate schreiben, doch es gibt bestimmte Operationen, die auf keinen Fall doppelt ausgefĂŒhrt werden dĂŒrfen. DafĂŒr gibt es das Merkmal des Leaders.

Rollenleben
Damit eine abstrakte Rolle in einer solchen Architektur existieren kann, muss das Framework sie irgendwie verwalten. NatĂŒrlich erfolgt die Verwaltung, ohne den Tarantool-Prozess neu zu starten. FĂŒr die Verwaltung von Rollen gibt es 4 Callbacks. Cartridge wird sie selbst aufrufen, je nachdem, was in der verteilten Konfiguration geschrieben steht, 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 einmal entweder beim Einschalten der Rolle oder beim Neustart von Tarantool aufgerufen. Hier ist es praktisch, beispielsweise box.space.create zu initialisieren, oder der Scheduler kann einen Hintergrundfaser starten, der in bestimmten Zeitintervallen Aufgaben ausfĂŒhrt.
Eine Funktion init kann nicht ausreichen. Cartridge ermöglicht es den Rollen, die verteilte Konfiguration zu nutzen, die sie zur Speicherung der Topologie verwendet. In derselben Konfiguration können wir einen neuen Abschnitt deklarieren und in diesem Fragment der GeschÀftskonfiguration speichern. In meinem Beispiel kann dies ein Datenschema oder die Einstellung des Schedulers sein.
Der Cluster ruft validate_config und apply_config bei jeder Ănderung der verteilten Konfiguration auf. Wenn die Konfiguration durch einen zweiphasigen Commit angewendet wird, ĂŒberprĂŒft der Cluster, ob jede Rolle bereit ist, diese neue Konfiguration zu akzeptieren, und informiert den Benutzer gegebenenfalls ĂŒber einen Fehler. Sobald alle zugestimmt haben, dass die Konfiguration in Ordnung ist, wird ausgefĂŒhrt apply_config.
AuĂerdem haben die Rollen eine Methode stop, die zur Bereinigung der Ergebnisse der AktivitĂ€ten der Rolle notwendig ist. Wenn wir sagen, dass der Scheduler auf diesem Server nicht mehr benötigt wird, kann er die Faser stoppen, die er mit Hilfe von init.
Rollen können miteinander interagieren. Wir gewöhnen uns daran, Funktionsaufrufe in Lua zu schreiben, aber es kann vorkommen, dass wir in diesem Prozess die benötigte Rolle nicht haben. Um Netzwerkaufrufe zu erleichtern, verwenden wir ein Hilfsmodul rpc (Remote Procedure Call), das auf dem standardmĂ€Ăigen Netbox basiert, das in Tarantool integriert ist. Das kann praktisch sein, wenn beispielsweise Ihr Gateway direkt den Scheduler auffordern möchte, die Arbeit sofort zu erledigen, anstatt einen Tag zu warten.
Ein weiterer wichtiger Punkt ist die GewĂ€hrleistung der Fehlertoleranz. Zum Monitoring der Gesundheit wird in Cartridge das SWIM-Protokoll verwendet. . Kurz gesagt, die Prozesse tauschen âGerĂŒchteâ ĂŒber UDP aus â jeder Prozess erzĂ€hlt seinen Nachbarn die neuesten Nachrichten, und sie antworten. Wenn plötzlich keine Antwort kommt, beginnt Tarantool, etwas VerdĂ€chtiges zu vermuten; nach einer gewissen Zeit beteuert es den Tod und beginnt, allen Anwesenden diese Neuigkeit zu erzĂ€hlen.

Basierend auf diesem Protokoll organisiert Cartridge die automatische Fehlerbehandlung. Jeder Prozess ĂŒberwacht seine Umgebung, und wenn der Leader plötzlich nicht mehr antwortet, kann eine Replica seine Rolle ĂŒbernehmen, wobei Cartridge die laufenden Rollen entsprechend konfiguriert.

Hier ist Vorsicht geboten, denn hĂ€ufiges Hin- und Herwechseln kann zu Datenkonflikten bei der Replikation fĂŒhren. Die automatische Failover-Funktion willkĂŒrlich zu aktivieren, macht natĂŒrlich keinen Sinn. Man muss genau wissen, was passiert, und sicher sein, dass die Replikation nicht kaputtgeht, nachdem der Leader sich erholt hat und ihm die Krone zurĂŒckgegeben wird.
Aus allem Gesagten könnte der Eindruck entstehen, dass Rollen wie Mikrodienste sind. In gewisser Hinsicht sind sie es auch, nur als Module innerhalb der Tarantool-Prozesse. Aber es gibt auch einige grundsĂ€tzliche Unterschiede. Erstens mĂŒssen alle Rollen des Projekts im selben Code-Repository leben. Und alle Tarantool-Prozesse mĂŒssen aus demselben Code-Repository gestartet werden, um Ăberraschungen zu vermeiden, wie zum Beispiel, dass wir versuchen, den Scheduler zu initialisieren, und dieser einfach nicht vorhanden ist. AuĂerdem sollten Unterschiede in den Codeversionen vermieden werden, da das Verhalten des Systems in einer solchen Situation sehr schwer vorherzusagen und zu debuggen ist.
Im Gegensatz zu Docker können wir nicht einfach ein 'Image' einer Rolle nehmen, es auf eine andere Maschine bringen und dort starten. Unsere Rollen sind nicht so isoliert wie Docker-Container. AuĂerdem können wir auf einem Instance nicht zwei identische Rollen ausfĂŒhren. Eine Rolle ist entweder vorhanden oder nicht, in gewissem Sinne ist sie ein Singleton. Und drittens mĂŒssen innerhalb der gesamten Replikationsgruppe die Rollen identisch sein, denn sonst wĂ€re es absurd â die Daten sind gleich, die Konfiguration jedoch unterschiedlich.
Deploy-Tools
Ich habe versprochen zu zeigen, wie Cartridge beim Deployment von Anwendungen hilft. Um die Arbeit fĂŒr alle zu erleichtern, verpackt das Framework RPM-Pakete:
$ cartridge pack rpm myapp -- verpackt fĂŒr uns .\/myapp-0.1.0-1.rpm
$ sudo yum install .\/myapp-0.1.0-1.rpmDas installierte Paket enthĂ€lt fast alles, was nötig ist: sowohl die Anwendung als auch die installierten Lua-AbhĂ€ngigkeiten. Tarantool wird auch als AbhĂ€ngigkeit des RPM-Pakets auf den Server kommen, und unser Service ist bereit zum Start. Dies geschieht ĂŒber systemd, aber zuerst muss ein wenig Konfiguration geschrieben werden. Mindestens die URI jedes Prozesses sollte angegeben werden. Drei fĂŒr das Beispiel sind 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}
CONFIGHier gibt es einen interessanten Aspekt. Anstatt nur den Port des BinĂ€rprotokolls anzugeben, geben wir die vollstĂ€ndige öffentliche Adresse des Prozesses einschlieĂlich des Hostnamens an. Dies ist notwendig, damit die Knoten im Cluster wissen, wie sie sich untereinander verbinden. Es ist eine schlechte 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 sie wird nichts funktionieren, daher wird Cartridge einfach keinen Knoten mit einer falschen advertise_uri starten.
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 Instanziierungs-Einheiten, die so funktionieren:
$ sudo systemctl start myapp@router
$ sudo systemctl start myapp@storage_A
$ sudo systemctl start myapp@storage_BIn der Konfiguration haben wir den HTTP-Port angegeben, auf dem Cartridge die WeboberflĂ€che bereitstellt â 8080. Lassen Sie uns darauf zugreifen und einen Blick darauf werfen:

Wir sehen, dass die Prozesse zwar gestartet wurden, aber noch nicht konfiguriert sind. Das Cartridge weiĂ noch nicht, wer mit wem replizieren soll und kann selbst keine Entscheidung treffen, daher wartet es auf unser Handeln. Unsere Auswahl ist nicht groĂ: Das Leben des neuen Clusters beginnt mit der Konfiguration des ersten Knotens. Dann fĂŒgen wir die anderen zum Cluster hinzu, weisen ihnen Rollen zu, und dann kann das Deployment als erfolgreich abgeschlossen betrachtet werden.
Lass uns ein GlÀschen unseres LieblingsgetrÀnks einschenken und uns nach einer langen Arbeitswoche entspannen. Die Anwendung kann benutzt werden.

Ergebnisse
Was sind die Ergebnisse? Probieren Sie es aus, nutzen Sie es, hinterlassen Sie Ihr Feedback, eröffnen Sie Tickets auf GitHub.
Links
[1]
[2]
[3]
[4]
[5]
[6]
Quelle: habr.com
