Tarantool-Patrone: Sharding eines Lua-Backends in drei Zeilen

Tarantool-Patrone: Sharding eines Lua-Backends in drei Zeilen

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 [1]. Es ermöglicht Ihnen, Daten auf mehrere Server zu verteilen, aber Sie müssen daran herumbasteln, um es einzurichten und die Geschäftslogik anzuhängen.

Gute Nachrichten: Wir haben einige große Aufnahmen gesammelt (z [2], [3]) und ein weiteres Framework erstellt, das die Lösung dieses Problems erheblich vereinfachen wird.

Tarantool-Patrone ist ein neues Framework zur Entwicklung komplexer verteilter Systeme. Dadurch können Sie sich auf das Schreiben von Geschäftslogik konzentrieren, anstatt Infrastrukturprobleme zu lösen. Im Folgenden erkläre ich Ihnen, wie dieses Framework funktioniert und wie Sie damit verteilte Dienste schreiben.

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.

Tarantool-Patrone: Sharding eines Lua-Backends in drei Zeilen

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.

Tarantool-Patrone: Sharding eines Lua-Backends in drei Zeilen

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.

Tarantool-Patrone: Sharding eines Lua-Backends in drei Zeilen

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.

Tarantool-Patrone: Sharding eines Lua-Backends in drei Zeilen

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:

Tarantool-Patrone: Sharding eines Lua-Backends in drei Zeilen

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:

Tarantool-Patrone: Sharding eines Lua-Backends in drei Zeilen

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.

Tarantool-Patrone: Sharding eines Lua-Backends in drei Zeilen

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 [4]. Kurz gesagt, Prozesse tauschen über UDP „Gerüchte“ miteinander aus – jeder Prozess teilt seinen Nachbarn die neuesten Nachrichten mit und diese antworten. Wenn plötzlich keine Antwort kommt, beginnt Tarantool zu vermuten, dass etwas nicht stimmt, und nach einer Weile rezitiert er den Tod und beginnt, allen um ihn herum diese Neuigkeiten zu erzählen.

Tarantool-Patrone: Sharding eines Lua-Backends in drei Zeilen

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.

Tarantool-Patrone: Sharding eines Lua-Backends in drei Zeilen

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:

Tarantool-Patrone: Sharding eines Lua-Backends in drei Zeilen

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.

Tarantool-Patrone: Sharding eines Lua-Backends in drei Zeilen

Ergebnisse

Was sind die Ergebnisse? Probieren Sie es aus, nutzen Sie es, hinterlassen Sie Feedback, erstellen Sie Tickets auf Github.

Referenzen

[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] Abrechnungsarchitektur der neuen Generation: Transformation mit dem Übergang zu Tarantool

[4] SWIM – Cluster-Konstruktionsprotokoll

[5] GitHub – tarantool/cartridge-cli

[6] GitHub – tarantool/cartridge

Source: habr.com

Kommentar hinzufügen