
Im Jahr 2017 haben wir den Wettbewerb zur Entwicklung des transaktionalen Kernels des Investmentgeschäfts der Alfa-Bank gewonnen und mit der Arbeit begonnen (auf HighLoad++ 2018 mit einem Bericht über den Kernel des Investmentgeschäfts Vladimir Drynkin, Leiter der Abteilung für den transaktionalen Kernel des Investmentgeschäfts der Alfa-Bank). Dieses System sollte Daten zu Transaktionen aus verschiedenen Quellen in unterschiedlichen Formaten aggregieren, die Daten in ein einheitliches Format bringen, sie speichern und den Zugriff darauf ermöglichen.
Im Verlauf der Entwicklung hat sich das System weiterentwickelt und an Funktionalität gewonnen. Irgendwann haben wir erkannt, dass sich etwas viel Größeres als nur ein Anwendungsprogramm herauskristallisiert, das für die Lösung eines festgelegten Aufgabenbereichs geschaffen wurde: Wir haben ein System zum Aufbau von verteilten Anwendungen mit persistentem Speicher. Die gesammelten Erfahrungen bildeten die Grundlage für ein neues Produkt — (TDG).
Ich möchte über die Architektur von TDG und die Lösungen berichten, die wir im Entwicklungsprozess gefunden haben, Sie mit der Hauptfunktionalität vertraut machen und zeigen, wie unser Produkt zur Basis für den Aufbau kompletter Lösungen werden kann.
Architektonisch haben wir das System in verschiedene Rollen, von denen jede für die Lösung eines bestimmten Aufgabenbereichs verantwortlich ist. Eine laufende Instanz der Anwendung implementiert einen oder mehrere Typen von Rollen. In einem Cluster können mehrere Rollen desselben Typs vorhanden sein:

Connector
Der Connector ist für die Verbindung zur Außenwelt verantwortlich; seine Aufgabe besteht darin, die Anfrage entgegenzunehmen, sie zu parsen, und wenn dies erfolgreich ist, die Daten zur Verarbeitung an den Input-Prozessor zu senden. Wir unterstützen die Formate HTTP, SOAP, Kafka, FIX. Die Architektur ermöglicht es, die Unterstützung neuer Formate einfach hinzuzufügen; bald wird auch die Unterstützung für IBM MQ verfügbar sein. Wenn das Parsen der Anfrage fehlschlägt, gibt der Connector einen Fehler zurück; andernfalls antwortet er, dass die Anfrage erfolgreich bearbeitet wurde, selbst wenn bei der weiteren Verarbeitung ein Fehler aufgetreten ist. Dies ist absichtlich so gestaltet, um mit Systemen zu arbeiten, die nicht in der Lage sind, Anfragen erneut zu senden – oder umgekehrt, dies zu häufig tun. Um Datenverluste zu vermeiden, wird eine Reparaturwarteschlange genutzt: Zunächst gelangt das Objekt in diese Warteschlange und wird erst nach erfolgreicher Verarbeitung daraus entfernt. Der Administrator kann Benachrichtigungen über Objekte erhalten, die in der Reparaturwarteschlange verbleiben, und nach Behebung des Software- oder Hardwarefehlers einen Wiederholungsversuch durchführen.
Input-Prozessor
Der Input-Prozessor klassifiziert die empfangenen Daten nach charakteristischen Merkmalen und ruft die passenden Handler auf. Die Handler sind in Lua geschrieben und werden in einer Sandbox ausgeführt, sodass sie das Funktionieren des Systems nicht beeinflussen können. In diesem Stadium können die Daten in das erforderliche Format gebracht und bei Bedarf eine beliebige Anzahl von Aufgaben gestartet werden, die die notwendige Logik umsetzen können. Beispielsweise im Produkt MDM (Master Data Management), das auf dem Tarantool Data Grid basiert, starten wir bei der Hinzufügung eines neuen Benutzers, um die Verarbeitung der Anfrage nicht zu verlangsamen, die Erstellung des Golden Record als separate Aufgabe. Die Sandbox unterstützt Lese-, Änderungs- und Hinzufügungsanfragen von Daten und erlaubt die Ausführung einer Funktion auf allen Rollen vom Typ storage sowie die Aggregation des Ergebnisses (map/reduce).
Handler können in Dateien beschrieben werden:
sum.lua
local x, y = unpack(...)
return x + yUnd dann in der Konfiguration deklariert werden:
functions:
sum: { __file: sum.lua }
Warum Lua? Lua ist eine sehr einfache Sprache. Aus unserer Erfahrung heraus beginnen die Leute nach ein paar Stunden des Kennenlernens, Code zu schreiben, der ihr Problem löst. Und das sind nicht nur professionelle Entwickler, sondern beispielsweise auch Analysten. Darüber hinaus arbeitet Lua dank des JIT-Compilers sehr schnell.
Speicher
Der Speicher hält persistente Daten. Vor der Speicherung werden die Daten auf Übereinstimmung mit dem Datenschema validiert. Für die Beschreibung des Schemas verwenden wir ein erweitertes Format. . Beispiel:
{
"name": "User",
"type": "record",
"logicalType": "Aggregate",
"fields": [
{ "name": "id", "type": "string"},
{"name": "first_name", "type": "string"},
{"name": "last_name", "type": "string"}
],
"indexes": ["id"]
}Aus dieser Beschreibung wird automatisch DDL (Data Definition Language) für das DBMS Tarantool und ein Schema für den Datenzugriff.
Es wird asynchrone Datenreplikation unterstützt (geplant ist die Hinzufügung synchroner Replikation).
Ausgabeverarbeiter
Manchmal müssen externe Verbraucher über das Eintreffen neuer Daten informiert werden; dafür gibt es die Rolle des Ausgabeverarbeiters. Nach der Speicherung der Daten können sie an den entsprechenden Verarbeiter übermittelt werden (zum Beispiel, um sie in das Format zu bringen, das der Verbraucher verlangt) – und danach an den Connector zur Versendung weitergegeben werden. Auch hier wird eine Reparaturwarteschlange genutzt: Wenn niemand das Objekt angenommen hat, kann der Administrator es später erneut versuchen.
Skalierung
Die Rollen des Connectors, des Eingangsverarbeiters und des Ausgabeverarbeiters sind zustandslos, was es uns ermöglicht, das System horizontal zu skalieren, indem wir einfach neue Instanzen der Anwendung mit aktivierten Rollen des benötigten Typs hinzufügen. Für die horizontale Skalierung des Speichers wird zur Organisation des Clusters unter Verwendung virtueller Buckets verwendet. Nach der Hinzufügung eines neuen Servers wird ein Teil der Buckets von den alten Servern im Hintergrund auf den neuen Server verschoben; dies geschieht transparent für die Benutzer und hat keine Auswirkungen auf die Gesamtfunktion des Systems.
Daten Eigenschaften
Objekte können sehr groß sein und andere Objekte enthalten. Wir gewährleisten die Atomarität beim Hinzufügen und Aktualisieren von Daten, indem wir das Objekt mit allen Abhängigkeiten in einen virtuellen Bucket speichern. Damit wird das "Verstreuen" des Objekts auf mehrere physische Server ausgeschlossen.
Versionsverwaltung wird unterstützt: Jedes Update eines Objekts erstellt eine neue Version, und wir können jederzeit einen zeitlichen Schnitt machen und sehen, wie die Welt damals aussah. Für Daten, die keine lange Historie benötigen, können wir die Anzahl der Versionen einschränken oder sogar nur eine – die letzte – speichern, was faktisch die Versionsverwaltung für einen bestimmten Typ deaktiviert. Auch eine zeitliche Begrenzung der Historie ist möglich: Beispielsweise können wir alle Objekte eines bestimmten Typs, die älter als ein Jahr sind, löschen. Auch die Archivierung wird unterstützt: Wir können Objekte, die älter als die angegebene Zeit sind, exportieren und so Platz im Cluster schaffen.
Aufgaben
Eine interessante Funktion ist die Möglichkeit, Aufgaben nach Zeitplan, auf Anfrage des Benutzers oder programmgesteuert aus der Sandbox auszuführen:

Hier sehen wir eine weitere Rolle – den Runner. Diese Rolle hat keinen Zustand, und bei Bedarf können zusätzliche Instanzen der Anwendung mit dieser Rolle zum Cluster hinzugefügt werden. Die Verantwortung des Runners besteht darin, Aufgaben auszuführen. Wie bereits erwähnt, können in der Sandbox neue Aufgaben generiert werden; sie werden in einer Warteschlange im Storage gespeichert und dann vom Runner ausgeführt. Dieser Aufgabentyp wird als Job bezeichnet. Außerdem haben wir einen Aufgabentyp, der Task genannt wird – das sind benutzerdefinierte Aufgaben, die nach Zeitplan (mit dem Cron-Syntax) oder nach Bedarf gestartet werden können. Um solche Aufgaben zu starten und zu überwachen, haben wir einen benutzerfreundlichen Aufgabenmanager. Damit diese Funktionalität verfügbar ist, muss die Rolle des Schedulers aktiviert werden; diese Rolle hat einen Zustand, ist also nicht skalierbar, was jedoch auch nicht erforderlich ist; gleichwohl kann sie, wie alle anderen Rollen auch, eine Replikation haben, die aktiv wird, falls der Master ausfällt.
Logger
Eine weitere Rolle nennt sich Logger. Sie sammelt Logs von allen Cluster-Mitgliedern und bietet eine Schnittstelle für den Export und die Ansicht über eine Weboberfläche.
Dienste
Es ist wichtig zu erwähnen, dass das System die einfache Erstellung von Diensten ermöglicht. In der Konfigurationsdatei kann angegeben werden, welche Anfragen an den benutzerdefinierten Handler, der in der Sandbox ausgeführt wird, gesendet werden sollen. In diesem Handler kann zum Beispiel eine analytische Anfrage ausgeführt und ein Ergebnis zurückgegeben werden.
Der Dienst wird in der Konfigurationsdatei beschrieben:
services:
sum:
doc: "addiert zwei Zahlen"
function: sum
return_type: int
args:
x: int
y: int
Die GraphQL-API wird automatisch generiert und der Service wird verfügbar für Aufrufe:
Abfrage {
sum(x: 1, y: 2)
} Dies führt zum Aufruf des Handlers sum, der das Ergebnis zurückgibt:
3
Profilierung von Abfragen und Metriken
Zur besseren Verständnis der Systemfunktionalität und der Abfragen haben wir die Unterstützung des OpenTracing-Protokolls implementiert. Das System kann auf Anfrage Informationen an Tools senden, die dieses Protokoll unterstützen, wie zum Beispiel Zipkin, um zu verstehen, wie die Anfrage bearbeitet wurde:

Natürlich bietet das System interne Metriken, die mit Prometheus gesammelt und mit Grafana visualisiert werden können.
Bereitstellung
Der Tarantool Data Grid kann aus RPM-Paketen oder Archiven sowie über ein Dienstprogramm aus der Lieferung oder Ansible bereitgestellt werden, außerdem gibt es Unterstützung für Kubernetes ().
Die Anwendung, die die Geschäftslogik implementiert (Konfiguration, Handler), wird in den bereitgestellten Tarantool Data Grid-Cluster in Form eines Archivs über die UI oder mithilfe eines Skripts über unsere API geladen.
Beispiele für Anwendungen
Welche Anwendungen können mit dem Tarantool Data Grid erstellt werden? Tatsächlich sind die meisten Geschäftsaufgaben auf die eine oder andere Weise mit der Verarbeitung von Datenströmen sowie der Speicherung und dem Zugriff darauf verbunden. Wenn Sie also große Datenströme haben, die zuverlässig gespeichert und abgerufen werden müssen, kann unser Produkt Ihnen viel Entwicklungszeit sparen und Sie können sich auf Ihre Geschäftslogik konzentrieren.
Zum Beispiel wollen wir Informationen über den Immobilienmarkt sammeln, um später beispielsweise Informationen über die profitabelsten Angebote zu haben. In diesem Fall definieren wir die folgenden Aufgaben:
- Bots, die Informationen aus offenen Quellen sammeln — das werden unsere Datenquellen sein. Diese Aufgabe können Sie mit fertigen Lösungen oder durch Schreiben von Code in jeder Sprache lösen.
- Im Anschluss wird der Tarantool Data Grid die Daten empfangen und speichern. Wenn das Datenformat aus verschiedenen Quellen unterschiedlich ist, können Sie Code in Lua schreiben, der die Daten in ein einheitliches Format umwandelt. In der Phase der Vorverarbeitung können Sie beispielsweise doppelte Angebote filtern oder die Datenbankinformationen über auf dem Markt tätige Agenten zusätzlich aktualisieren.
- Jetzt haben Sie bereits eine skalierbare Lösung im Cluster, die Sie mit Daten füllen und aus der Sie Daten abfragen können. Danach können Sie neue Funktionen implementieren, zum Beispiel einen Dienst schreiben, der eine Anfrage an die Daten stellt und das vorteilhafteste Angebot innerhalb eines Tages ausgibt — dafür sind nur einige Zeilen in der Konfigurationsdatei und ein wenig Lua-Code erforderlich.
Was folgt jetzt?
In unserer Priorität steht die Verbesserung der Entwicklungsfreundlichkeit durch . Zum Beispiel ist dies eine IDE mit Unterstützung für die Profilerstellung und das Debugging von Handlern, die in einer Sandbox arbeiten.
Außerdem legen wir großen Wert auf Sicherheitsfragen. Momentan befinden wir uns im Zertifizierungsprozess bei der FSTEC Russlands, um ein hohes Sicherheitsniveau zu bestätigen und den geltenden Anforderungen für die Zertifizierung von Softwareprodukten, die in Informationssystemen personenbezogener Daten und staatlichen Informationssystemen verwendet werden, gerecht zu werden.
Quelle: habr.com
