Am 26. Februar fand das Meetup zu Apache Ignite GreenSource statt, bei dem Mitwirkende des Open-Source-Projekts sprachen. . Ein bedeutendes Ereignis für die Gemeinschaft war die Umstrukturierung des Components , der es ermöglicht, benutzerdefinierte Microservices direkt im Ignite-Cluster bereitzustellen. Über diesen komplexen Prozess sprach , Software-Ingenieur und seit über zwei Jahren Mitwirkender bei Apache Ignite.

Beginnen wir damit, was Apache Ignite überhaupt ist. Es handelt sich um eine Datenbank, die ein verteiltes Key/Value-Speichersystem mit Unterstützung für SQL, Transaktionen und Caching darstellt. Darüber hinaus ermöglicht Ignite die Bereitstellung benutzerdefinierter Dienste direkt im Ignite-Cluster. Entwicklern stehen alle Werkzeuge zur Verfügung, die Ignite bietet – verteilte Datentypen, Messaging, Streaming, Rechen- und Daten-Grid. Zum Beispiel mit der Verwendung des Data Grid entfällt das Problem der Verwaltung einer separaten Infrastruktur für die Datenspeicherung und damit verbundene Kosten.

Mit der API des Service Grids kann ein Dienst einfach bereitgestellt werden, indem in der Konfiguration das Bereitstellungsschema und der Dienst selbst angegeben werden.
In der Regel beschreibt das Deployment-Schema die Anzahl der Instanzen, die auf den Knoten des Clusters bereitgestellt werden sollen. Es gibt zwei typische Deployment-Schemata. Das erste ist das Cluster Singleton: Zu jedem Zeitpunkt ist garantiert eine Instanz des Benutzerdienstes im Cluster verfügbar. Das zweite ist das Node Singleton: Auf jedem Knoten des Clusters ist eine Instanz des Dienstes bereitgestellt.

Der Benutzer kann auch die Anzahl der Instanzen des Dienstes im gesamten Cluster angeben und ein Prädikat zur Filterung geeigneter Knoten definieren. In diesem Szenario berechnet der Service Grid selbst die optimale Verteilung für die Bereitstellung der Dienste.
Zusätzlich gibt es eine Funktion namens Affinity Service. Affinity ist eine Funktion, die die Beziehung zwischen Schlüsseln und Partitionen sowie zwischen Partitionen und Knoten in der Topologie definiert. Anhand des Schlüssels kann der primäre Knoten identifiziert werden, auf dem die Daten gespeichert sind. Dadurch kann Ihr eigener Dienst mit dem Schlüssel und dem Cache der Affinity-Funktion verknüpft werden. Im Falle einer Änderung der Affinity-Funktion erfolgt eine automatische Neudepoyierung. So wird der Dienst immer in der Nähe der Daten platziert, mit denen er arbeiten soll, was die Zugriffsüberheadkosten auf die Informationen reduziert. Ein solches Schema kann als eine Art kollokierte Berechnungen bezeichnet werden.
Jetzt, da wir die Vorzüge des Service Grids erörtert haben, erzählen wir von seiner Entwicklungsgeschichte.
Was war früher
Die vorherige Implementierung des Service Grids basierte auf einem transaktionalen replizierten Systemcache Ignite. Mit dem Wort „Cache“ in Ignite ist ein Speicher gemeint. Es handelt sich also nicht um etwas Vorübergehendes, wie man vielleicht denken könnte. Obwohl der Cache replizierbar ist und jeder Knoten den gesamten Datensatz enthält, hat der Cache intern eine partitionierte Darstellung. Dies hängt mit der Optimierung von Speichersystemen zusammen.

Was geschah, als der Benutzer einen Dienst bereitstellen wollte?
- Alle Knoten im Cluster abonnierten die Datenaktualisierung im Speicher über den integrierten Mechanismus der Continuous Query.
- Der Initiator-Knoten führte unter einer read-committed Transaktion einen Eintrag in die Datenbank ein, der die Dienstkonfiguration einschließlich des serialisierten Instanz enthielt.
- Nach Erhalt der Benachrichtigung über den neuen Eintrag berechnete der Koordinator die Verteilung basierend auf der Konfiguration. Das erhaltene Objekt wurde zurück in die Datenbank geschrieben.
- Wenn ein Knoten in die Verteilung fiel, musste der Koordinator ihn bereitstellen.
Was uns nicht gefiel
Irgendwann kamen wir zu dem Schluss: So kann man nicht mit Diensten arbeiten. Es gab mehrere Gründe.
Wenn während des Deployments ein Fehler auftrat, konnte man nur aus den Protokollen des Knotenpunktes erfahren, wo es passiert ist. Es gab nur ein asynchrones Deployment, sodass nach der Übergabe der Kontrolle an den Benutzer vom Deployment-Methoden einige zusätzliche Zeit erforderlich war, um den Service zu starten – in dieser Zeit hatte der Benutzer keine Möglichkeiten zur Steuerung. Um das Service Grid weiterzuentwickeln, neue Funktionen zu erstellen, neue Benutzer zu gewinnen und das Leben aller einfacher zu gestalten, muss etwas geändert werden.
Bei der Planung des neuen Service Grids wollten wir in erster Linie die Garantie eines synchronen Deployments bieten: Sobald der Benutzer die Kontrolle vom API zurückerhält, kann er sofort die Dienste nutzen. Außerdem wollten wir dem Initiator die Möglichkeit geben, Fehler beim Deployment zu bearbeiten.
Darüber hinaus wollten wir die Implementierung erleichtern und von Transaktionen und Rebalancing absehen. Obwohl der Cache replizierbar ist und keine Balancierung stattfindet, traten bei großen Deployments mit zahlreichen Knoten punkten Probleme auf. Bei Änderungen der Topologie müssen die Knoten Informationen austauschen, und bei großflächigen Deployments können diese Daten sehr umfangreich sein.
Wenn die Topologie instabil war, musste der Koordinator die Verteilung der Dienste neu berechnen. Generell können Arbeiten mit Transaktionen in einer instabilen Topologie zu schwer vorhersagbaren Fehlern führen.
Die Probleme
Was wären globale Veränderungen ohne begleitende Probleme? Eines davon war die Änderung der Topologie. Es ist wichtig zu verstehen, dass jederzeit, selbst während der Bereitstellung eines Dienstes, ein Knoten in den Cluster ein- oder austreten kann. Darüber hinaus, wenn ein Knoten während der Bereitstellung in den Cluster eintritt, muss alle Informationen über die Dienste konsistent an den neuen Knoten übergeben werden. Dabei geht es nicht nur um das, was bereits bereitgestellt wurde, sondern auch um aktuelle und zukünftige Bereitstellungen.
Das ist nur eines der Probleme, die man in eine eigene Liste aufnehmen kann:
- Wie installiert man statisch konfigurierte Dienste beim Start eines Knotens?
- Was tun, wenn ein Knoten den Cluster verlässt und Dienste gehostet hat?
- Was tun, wenn der Koordinator gewechselt hat?
- Was tun, wenn sich der Kunde wieder mit dem Cluster verbunden hat?
- Müssen Aktivierungs-/Deaktivierungsanfragen verarbeitet werden und wie?
- Was, wenn die Cache-Zerstörung aufgerufen wurde, während wir Affinitätsdienste daran gebunden haben?
Und das ist bei weitem nicht alles.
Lösung
Als Ziel haben wir einen Event-Driven-Ansatz gewählt, der die Kommunikation zwischen Prozessen über Nachrichten realisiert. In Ignite sind bereits zwei Komponenten implementiert, die es Knoten ermöglichen, Nachrichten untereinander auszutauschen – communication-spi und discovery-spi.

Communication-spi ermöglicht es den Knoten, direkt zu kommunizieren und Nachrichten auszutauschen. Es eignet sich gut für die Übertragung großer Datenmengen. Discovery-spi ermöglicht es, eine Nachricht an alle Knoten im Cluster zu senden. In der Standardimplementierung erfolgt dies nach der Topologie 'Ring'. Außerdem gibt es eine Integration mit Zookeeper, wobei in diesem Fall die Topologie 'Stern' verwendet wird. Ein wichtiger Punkt ist: discovery-spi bietet Garantien dafür, dass die Nachricht in der richtigen Reihenfolge an alle Knoten zugestellt wird.
Betrachten wir das Bereitstellungsprotokoll. Alle Benutzeranfragen zur Bereitstellung und De-Bereitstellung werden über discovery-spi gesendet. Dies bietet folgende Garantien:
- Die Anfrage wird von allen Knoten im Cluster empfangen. Dies ermöglicht die Fortsetzung der Bearbeitung der Anfrage beim Wechsel des Koordinators. Zudem bedeutet dies, dass bei einer Nachricht jeder Knoten über alle notwendigen Metadaten verfügt, wie die Konfiguration des Dienstes und seine serialisierte Instanz.
- Die strikte Reihenfolge der Nachrichtenübermittlung ermöglicht die Lösung von Konfigurationskonflikten und konkurrierenden Anfragen.
- Da der Eintritt des Knotens in die Topologie ebenfalls über das Discovery SPI verarbeitet wird, erhält der neue Knoten alle notwendigen Daten für die Arbeit mit den Diensten.
Bei Erhalt einer Anfrage validieren die Knoten im Cluster diese und erstellen Aufgaben für die Verarbeitung. Diese Aufgaben werden in eine Warteschlange eingefügt und anschließend in einem anderen Thread von einem separaten Worker bearbeitet. Dies ist so umgesetzt, da das Deployment erhebliche Zeit in Anspruch nehmen kann und die Verzögerung eines teuren Discovery-Streams nicht akzeptabel ist.
Alle Anfragen aus der Warteschlange werden vom Deployment-Manager bearbeitet. Dieser verfügt über einen speziellen Worker, der eine Aufgabe aus dieser Warteschlange entnimmt und sie initialisiert, um mit der Bereitstellung zu beginnen. Danach erfolgen die folgenden Schritte:
- Jeder Knoten berechnet die Verteilung eigenständig dank einer neuen deterministischen Zuweisungsfunktion.
- Knoten erstellen eine Nachricht mit den Deployment-Ergebnissen und senden diese an den Koordinator.
- Der Koordinator aggregiert alle Nachrichten und erstellt das Ergebnis des gesamten Deployment-Prozesses, das über discovery-spi an alle Knoten im Cluster gesendet wird.
- Beim Empfang des Ergebnisses wird der Deployment-Prozess abgeschlossen, danach wird die Aufgabe aus der Warteschlange entfernt.

Neues ereignisgesteuertes Design: org.apache.ignite.internal.processors.service.IgniteServiceProcessor.java
Wenn während des Deployments ein Fehler auftritt, wird dieser sofort in die Nachricht aufgenommen, die an den Koordinator gesendet wird. Nach der Aggregation der Nachrichten hat der Koordinator Informationen über alle Fehler während des Deployments und sendet diese Nachricht über discovery-spi. Informationen zu den Fehlern sind an jedem Knoten im Cluster verfügbar.
Nach diesem Arbeitsalgorithmus werden alle wichtigen Ereignisse im Service Grid verarbeitet. Zum Beispiel ist ein Topologiewechsel ebenfalls eine Nachricht über discovery-spi. Insgesamt ist das Protokoll im Vergleich zu früheren Versionen ziemlich leichtgewichtig und zuverlässig. So zuverlässig, dass es jede Situation während des Deployments verarbeiten kann.
Was kommt als Nächstes?
Jetzt zu den Plänen. Jede größere Überarbeitung im Ignite-Projekt wird als Initiative zur Verbesserung von Ignite, das sogenannte IEP, umgesetzt. Auch das Redesign des Service Grids hat ein IEP — mit dem humorvollen Titel „Ölwechsel im Service Grid“. Tatsächlich haben wir jedoch nicht nur das Öl im Motor gewechselt, sondern den Motor komplett ersetzt.
Die Aufgaben im IEP haben wir in zwei Phasen unterteilt. Die erste Phase ist die umfangreiche Phase, die die Überarbeitung des Deploy-Protokolls umfasst. Diese ist bereits in den Master integriert, sodass Sie den neuen Service Grid in Version 2.8 ausprobieren können. Die zweite Phase beinhaltet eine Vielzahl weiterer Aufgaben:
- Hot Redeploy
- Versionierung der Dienste
- Erhöhung der Ausfallsicherheit
- Dünner Client
- Überwachungs- und Metrik-Tools
Zum Schluss können wir Ihnen das Service Grid für den Aufbau von ausfallsicheren Systemen hoher Verfügbarkeit empfehlen. Wir laden Sie auch ein, sich an uns in der und zu beteiligen und Ihre Erfahrungen zu teilen. Ihre Erfahrungen sind für die Community wirklich wichtig und helfen zu verstehen, in welche Richtung wir uns weiterentwickeln und wie wir die Komponente in Zukunft gestalten können.
Quelle: habr.com
