Bei der Mail.ru Group haben wir Tarantool – das ist ein Anwendungsserver in Lua, der auch als Datenbank dient (oder umgekehrt?). Es ist schnell und cool, aber die Fähigkeiten eines Servers sind immer noch nicht unbegrenzt. Auch die vertikale Skalierung ist kein Allheilmittel, daher verfügt Tarantool über Tools für die horizontale Skalierung – das vshard-Modul
Gute Nachrichten: Wir haben einige große Aufnahmen gesammelt (z
Und was genau ist das Problem?
Wir haben eine Vogelspinne, wir haben Vshard – was will man mehr?
Erstens ist es eine Frage der Bequemlichkeit. Die vshard-Konfiguration wird über Lua-Tabellen konfiguriert. Damit ein verteiltes System aus 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 Bereitstellungssystemen verwendet.
Die Patrone selbst verwaltet die vshard-Konfiguration, sie tut dies auf der Grundlage ihrer eigene verteilte Konfiguration. Es handelt sich im Wesentlichen um eine einfache YAML-Datei, von der in jeder Tarantool-Instanz eine Kopie gespeichert wird. Die Vereinfachung besteht darin, dass das Framework selbst seine Konfiguration überwacht und sicherstellt, dass diese überall gleich ist.
Zweitens ist es wiederum eine Frage der Bequemlichkeit. 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, sprechen wir am häufigsten über einzelne Komponenten und deren Zusammenspiel. Es ist noch zu früh, über die Einführung eines Clusters auf drei Rechenzentren nachzudenken.
Wir haben diese Probleme immer wieder gelöst und irgendwann ist es uns gelungen, einen Ansatz zu entwickeln, der die Arbeit mit der Anwendung über den gesamten Lebenszyklus hinweg vereinfacht: Erstellung, Entwicklung, Test, CI/CD, Wartung.
Cartridge führt das Konzept einer Rolle für jeden Tarantool-Prozess ein. Rollen sind ein Konzept, das es einem Entwickler ermöglicht, sich auf das Schreiben von Code zu konzentrieren. Alle im Projekt verfügbaren Rollen können auf einer Tarantool-Instanz ausgeführt werden, was für Tests ausreicht.
Hauptmerkmale der Tarantool-Patrone:
- automatisierte Cluster-Orchestrierung;
- Erweiterung der Funktionalität der Anwendung durch neue Rollen;
- Anwendungsvorlage für Entwicklung und Bereitstellung;
- integriertes automatisches Sharding;
- Integration mit dem Luatest-Testframework;
- Clusterverwaltung mit WebUI und API;
- Paketierungs- und Bereitstellungstools.
Hallo Welt!
Ich kann es kaum erwarten, das Framework selbst zu zeigen, daher heben wir die Geschichte der Architektur für später auf und beginnen mit etwas Einfachem. Wenn wir davon ausgehen, dass Tarantool selbst bereits installiert ist, müssen wir nur noch etwas tun
$ tarantoolctl rocks install cartridge-cli
$ export PATH=$PWD/.rocks/bin/:$PATH
Mit diesen beiden Befehlen werden die Befehlszeilen-Dienstprogramme installiert und Sie können Ihre erste Anwendung aus der Vorlage erstellen:
$ cartridge create --name myapp
Und das bekommen 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 einem vorgefertigten „Hello, World!“ Anwendung. Versuchen wir, es sofort auszuführen, nachdem wir zuvor die Abhängigkeiten (einschließlich des Frameworks selbst) installiert haben:
$ tarantoolctl rocks make
$ ./init.lua --http-port 8080
Wir haben also einen Knoten, der für die zukünftige Sharding-Anwendung ausgeführt wird. Ein neugieriger Laie kann sofort die Weboberfläche öffnen, mit der Maus einen Cluster aus einem Knoten konfigurieren und das Ergebnis genießen, aber es ist noch zu früh, um sich zu freuen. Bisher kann die Anwendung nichts Nützliches tun, daher erzähle ich Ihnen später von der Bereitstellung, aber jetzt ist es an der Zeit, Code zu schreiben.
Anwendungsentwicklung
Stellen Sie sich vor, wir entwerfen ein Projekt, das einmal täglich Daten empfangen, speichern und einen Bericht erstellen muss.
Wir beginnen mit dem Zeichnen eines Diagramms 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 den Speicher zu; dafür ist der Router da, dafür wurde er geschaffen.
Dieses Diagramm stellt immer noch nicht genau dar, was wir in dem Projekt erstellen werden, da die Komponenten abstrakt aussehen. Wir müssen noch sehen, wie dies auf das echte Tarantool projiziert wird – lasst uns unsere Komponenten nach Prozess gruppieren.
Es macht wenig Sinn, vshard-router und Gateway auf separaten Instanzen zu belassen. Warum müssen wir noch einmal im Netzwerk surfen, wenn dies bereits in der Verantwortung des Routers liegt? Sie müssen im selben Prozess ausgeführt werden. Das heißt, sowohl das Gateway als auch vshard.router.cfg werden in einem Prozess initialisiert und lassen sie lokal 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, drei Instanzen von Tarnatool zu starten. Ich muss Tests durchführen und überprüfen, ob ich „Gateway“ richtig geschrieben habe. Oder vielleicht möchte ich meinen Kollegen eine Funktion demonstrieren. Warum sollte ich mir die Mühe machen, drei Kopien bereitzustellen? So entstand das Konzept der Rollen. Eine Rolle ist ein reguläres Lash-Modul, dessen Lebenszyklus von Cartridge verwaltet wird. In diesem Beispiel gibt es vier davon – Gateway, Router, Speicher, Scheduler. Möglicherweise gibt es in einem anderen Projekt noch mehr. Alle Rollen können in einem Prozess ausgeführt werden, und das reicht aus.
Und wenn es um die Bereitstellung im Staging oder in der Produktion geht, weisen wir jedem Tarantool-Prozess abhängig von den Hardwarefunktionen einen eigenen Rollensatz zu:
Topologiemanagement
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 Cluster-Topologie. Hier sind 3 Replikationsgruppen von 5 Tarantool-Prozessen:
Da wir keine Daten verlieren möchten, gehen wir sorgfältig mit Informationen über laufende Prozesse um. Das Modul verfolgt die Konfiguration mithilfe eines zweiphasigen Commits. Sobald wir die Konfiguration aktualisieren möchten, prüft es zunächst, ob alle Instanzen verfügbar und bereit sind, die neue Konfiguration zu akzeptieren. Danach wendet die zweite Phase die Konfiguration an. Selbst wenn sich herausstellt, dass eine Kopie vorübergehend nicht verfügbar ist, wird also nichts Schlimmes passieren. Die Konfiguration wird einfach nicht übernommen und es wird Ihnen vorab eine Fehlermeldung angezeigt.
Außerdem wird im Abschnitt „Topologie“ ein so wichtiger Parameter wie der Leiter jeder Replikationsgruppe angegeben. Normalerweise ist dies die Kopie, 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. Es gibt jedoch einige Vorgänge, die auf keinen Fall zweimal ausgeführt werden sollten. Dafür gibt es ein Zeichen eines Führers.
Leben der Rollen
Damit in einer solchen Architektur eine abstrakte Rolle existiert, muss das Framework sie irgendwie verwalten. Die Steuerung erfolgt selbstverständlich ohne Neustart des Tarantool-Prozesses. Es gibt 4 Rückrufe zum Verwalten von Rollen. Die Patrone selbst ruft sie abhängig davon auf, was in ihrer verteilten Konfiguration geschrieben steht, und wendet so die Konfiguration auf bestimmte Rollen an.
function init()
function validate_config()
function apply_config()
function stop()
Jede Rolle hat eine Funktion init
. 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 in bestimmten Zeitintervallen Arbeiten ausführt.
Eine Funktion init
reicht möglicherweise nicht aus. Mit der Patrone können Rollen die verteilte Konfiguration nutzen, die zum Speichern der Topologie verwendet wird. Wir können einen neuen Abschnitt in derselben Konfiguration deklarieren und darin ein Fragment der Geschäftskonfiguration speichern. In meinem Beispiel könnte dies ein Datenschema oder Zeitplaneinstellungen für die Planerrolle sein.
Cluster-Aufrufe validate_config
и apply_config
jedes Mal, wenn sich die verteilte Konfiguration ändert. Wenn eine Konfiguration durch einen zweiphasigen Commit angewendet wird, prüft der Cluster, ob jede Rolle bereit ist, diese neue Konfiguration zu akzeptieren, und meldet dem Benutzer gegebenenfalls einen Fehler. Wenn sich alle einig sind, dass die Konfiguration normal ist, dann apply_config
.
Auch Rollen haben eine Methode stop
, was zum Bereinigen der Ausgabe der Rolle erforderlich ist. Wenn wir sagen, dass der Scheduler auf diesem Server nicht mehr benötigt wird, kann er die Fasern stoppen, mit denen er begonnen hat init
.
Rollen können miteinander interagieren. Wir sind es gewohnt, Funktionsaufrufe in Lua zu schreiben, aber es kann vorkommen, dass ein bestimmter Prozess nicht die Rolle hat, die wir brauchen. Um Anrufe über das Netzwerk 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 Planer direkt bitten möchte, die Arbeit sofort zu erledigen, anstatt einen Tag zu warten.
Ein weiterer wichtiger Punkt ist die Gewährleistung der Fehlertoleranz. Die Patrone verwendet das SWIM-Protokoll zur Überwachung des Zustands
Basierend auf diesem Protokoll organisiert Cartridge die automatische Fehlerbearbeitung. Jeder Prozess überwacht seine Umgebung, und wenn der Anführer plötzlich nicht mehr reagiert, kann das Replikat seine Rolle übernehmen und Cartridge konfiguriert die laufenden Rollen entsprechend.
Hier ist Vorsicht geboten, denn häufiges Hin- und Herwechseln kann zu Datenkonflikten bei der Replikation führen. Natürlich sollten Sie das automatische Failover nicht wahllos aktivieren. Wir müssen klar verstehen, was geschieht, und sicherstellen, dass die Replikation nicht abbricht, nachdem der Anführer wiederhergestellt und ihm die Krone zurückgegeben wurde.
Aus all dem könnte man den Eindruck gewinnen, dass Rollen Microservices ähneln. In gewissem Sinne sind sie genau das, nur als Module innerhalb von Tarantool-Prozessen. Es gibt aber auch eine Reihe grundsätzlicher Unterschiede. Erstens müssen sich alle Projektrollen in derselben Codebasis befinden. Und alle Tarantool-Prozesse sollten von derselben Codebasis aus gestartet werden, damit es keine Überraschungen gibt, wie wenn wir versuchen, den Scheduler zu initialisieren, dieser aber einfach nicht existiert. Außerdem sollten Sie keine Unterschiede in den Codeversionen zulassen, 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 Rollen-„Image“ übernehmen, es 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 einer Instanz ausführen. Eine Rolle existiert entweder oder sie existiert nicht; in gewissem Sinne ist sie ein Singleton. Und drittens müssen die Rollen innerhalb der gesamten Replikationsgruppe gleich sein, denn sonst wäre es absurd – die Daten sind gleich, aber die Konfiguration ist unterschiedlich.
Bereitstellungstools
Ich habe versprochen, zu zeigen, wie Cartridge bei der Bereitstellung von Anwendungen hilft. Um anderen das Leben zu erleichtern, packt 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 brauchen: sowohl die Anwendung als auch die installierten Abhängigkeiten. Tarantool wird auch als Abhängigkeit des RPM-Pakets auf dem Server ankommen und unser Dienst ist startbereit. Dies geschieht über systemd, aber zuerst müssen Sie eine kleine Konfiguration schreiben. Geben Sie mindestens den URI jedes Prozesses an. Drei reichen zum Beispiel aus.
$ 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ärprotokoll-Port anzugeben, geben wir die gesamte öffentliche Adresse des Prozesses einschließlich des Hostnamens an. Dies ist notwendig, damit die Clusterknoten wissen, wie sie sich miteinander verbinden. Es ist keine gute Idee, 0.0.0.0 als Advertise_uri-Adresse zu verwenden; es sollte eine externe IP-Adresse und keine Socket-Bindung sein. Ohne sie funktioniert nichts, sodass Sie mit Cartridge einfach keinen Knoten mit der falschen Advertise_uri starten können.
Nachdem die Konfiguration nun fertig ist, können Sie mit den Prozessen beginnen. Da eine reguläre Systemd-Einheit nicht den Start von mehr als einem Prozess zulässt, werden Anwendungen auf der Patrone durch die sogenannten installiert. 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, auf dem Cartridge das Webinterface bedient – 8080. Schauen wir uns das mal an:
Wir sehen, dass die Prozesse zwar laufen, aber noch nicht konfiguriert sind. Die Patrone weiß noch nicht, wer mit wem replizieren soll und kann keine eigene Entscheidung treffen, also wartet sie auf unser Handeln. Aber wir haben keine große Wahl: Das Leben eines neuen Clusters beginnt mit der Konfiguration des ersten Knotens. Dann fügen wir die anderen zum Cluster hinzu, weisen ihnen Rollen zu und an diesem Punkt kann die Bereitstellung als erfolgreich abgeschlossen betrachtet werden.
Lassen Sie uns nach einer langen Arbeitswoche ein Glas Ihres Lieblingsgetränks einschenken und entspannen. Die Anwendung kann verwendet werden.
Ergebnisse
Was sind die Ergebnisse? Probieren Sie es aus, nutzen Sie es, hinterlassen Sie Feedback, erstellen Sie Tickets auf Github.
Referenzen
[1]
Source: habr.com