Nicht nur Verarbeitung: Wie wir aus Kafka Streams eine verteilte Datenbank erstellt haben und was dabei herausgekommen ist

Hey Habr!

Wir erinnern Sie daran, dass Sie dem Buch folgen Kafkaeske Zustände Wir haben ein ebenso interessantes Werk über die Bibliothek veröffentlicht Kafka Streams-API.

Nicht nur Verarbeitung: Wie wir aus Kafka Streams eine verteilte Datenbank erstellt haben und was dabei herausgekommen ist

Derzeit lernt die Community erst die Grenzen dieses leistungsstarken Tools kennen. Daher wurde kürzlich ein Artikel veröffentlicht, dessen Übersetzung wir Ihnen gerne vorstellen möchten. Aus eigener Erfahrung erzählt der Autor, wie man Kafka Streams in einen verteilten Datenspeicher verwandelt. Viel Spaß beim Lesen!

Apache-Bibliothek Kafka-Bäche Wird weltweit in Unternehmen für die verteilte Stream-Verarbeitung auf Basis von Apache Kafka eingesetzt. Einer der unterschätzten Aspekte dieses Frameworks besteht darin, dass es die Speicherung lokaler Zustände ermöglicht, die auf der Thread-Verarbeitung basieren.

In diesem Artikel erzähle ich Ihnen, wie es unserem Unternehmen gelungen ist, diese Chance bei der Entwicklung eines Produkts für die Sicherheit von Cloud-Anwendungen gewinnbringend zu nutzen. Mithilfe von Kafka Streams haben wir Shared-State-Microservices erstellt, die jeweils als fehlertolerante und hochverfügbare Quelle zuverlässiger Informationen über den Status von Objekten im System dienen. Für uns ist dies sowohl hinsichtlich der Zuverlässigkeit als auch der Supportfreundlichkeit ein Fortschritt.

Wenn Sie an einem alternativen Ansatz interessiert sind, der es Ihnen ermöglicht, eine einzige zentrale Datenbank zur Unterstützung des formalen Zustands Ihrer Objekte zu verwenden, lesen Sie ihn, es wird interessant sein ...

Warum wir dachten, es sei an der Zeit, die Art und Weise zu ändern, wie wir mit Shared State arbeiten

Wir mussten den Status verschiedener Objekte auf der Grundlage von Agentenberichten aufrechterhalten (z. B.: Wurde die Website angegriffen)? Vor der Migration zu Kafka Streams haben wir uns häufig auf eine einzige zentrale Datenbank (+ Service-API) für die Statusverwaltung verlassen. Dieser Ansatz hat seine Nachteile: datumsintensive Situationen Die Aufrechterhaltung von Konsistenz und Synchronisierung wird zu einer echten Herausforderung. Die Datenbank kann zu einem Engpass werden oder in einer solchen Situation enden Rennbedingung und leiden unter Unvorhersehbarkeit.

Nicht nur Verarbeitung: Wie wir aus Kafka Streams eine verteilte Datenbank erstellt haben und was dabei herausgekommen ist

Abbildung 1: Ein typisches Split-State-Szenario vor dem Übergang zu
Kafka und Kafka Streams: Agenten kommunizieren ihre Ansichten über die API, der aktualisierte Status wird über eine zentrale Datenbank berechnet

Lernen Sie Kafka Streams kennen, das die Erstellung gemeinsamer Zustands-Microservices vereinfacht

Vor etwa einem Jahr haben wir beschlossen, unsere gemeinsamen Zustandsszenarien genau unter die Lupe zu nehmen, um diese Probleme anzugehen. Wir haben uns sofort entschieden, Kafka Streams auszuprobieren – wir wissen, wie skalierbar, hochverfügbar und fehlertolerant es ist und welche umfangreichen Streaming-Funktionen es bietet (Transformationen, einschließlich Stateful-Transformationen). Genau das, was wir brauchten, ganz zu schweigen davon, wie ausgereift und zuverlässig das Nachrichtensystem in Kafka geworden ist.

Jeder der von uns erstellten zustandsbehafteten Mikrodienste wurde auf einer Kafka Streams-Instanz mit einer relativ einfachen Topologie aufgebaut. Es bestand aus 1) einer Quelle 2) einem Prozessor mit einem dauerhaften Schlüsselwertspeicher 3) einer Senke:

Nicht nur Verarbeitung: Wie wir aus Kafka Streams eine verteilte Datenbank erstellt haben und was dabei herausgekommen ist

Abbildung 2: Die Standardtopologie unserer Streaming-Instanzen für zustandsbehaftete Microservices. Beachten Sie, dass es hier auch ein Repository gibt, das Planungsmetadaten enthält.

Bei diesem neuen Ansatz verfassen Agenten Nachrichten, die in das Quellthema eingespeist werden, und Verbraucher – beispielsweise ein E-Mail-Benachrichtigungsdienst – erhalten den berechneten gemeinsamen Status über die Senke (Ausgabethema).

Nicht nur Verarbeitung: Wie wir aus Kafka Streams eine verteilte Datenbank erstellt haben und was dabei herausgekommen ist

Abbildung 3: Neuer Beispiel-Aufgabenablauf für ein Szenario mit gemeinsam genutzten Microservices: 1) Der Agent generiert eine Nachricht, die am Kafka-Quellthema ankommt; 2) ein Mikrodienst mit gemeinsamem Status (unter Verwendung von Kafka Streams) verarbeitet ihn und schreibt den berechneten Status in das endgültige Kafka-Thema; Danach akzeptieren 3) die Verbraucher den neuen Zustand

Hey, dieser integrierte Schlüsselwertspeicher ist tatsächlich sehr nützlich!

Wie oben erwähnt, enthält unsere Shared-State-Topologie einen Schlüsselwertspeicher. Wir haben mehrere Möglichkeiten gefunden, es zu nutzen, zwei davon werden im Folgenden beschrieben.

Option Nr. 1: Verwenden Sie einen Schlüsselwertspeicher für Berechnungen

Unser erster Schlüsselwertspeicher enthielt die Hilfsdaten, die wir für die Berechnungen benötigten. Beispielsweise wurde in einigen Fällen der gemeinsame Staat durch das Prinzip der „Mehrheitsstimmen“ bestimmt. Das Repository könnte alle aktuellen Agentenberichte über den Status eines Objekts enthalten. Wenn wir dann von dem einen oder anderen Agenten einen neuen Bericht erhielten, konnten wir ihn speichern, Berichte aller anderen Agenten über den Zustand desselben Objekts aus dem Speicher abrufen und die Berechnung wiederholen.
Abbildung 4 unten zeigt, wie wir den Schlüssel-/Wertspeicher der Verarbeitungsmethode des Prozessors ausgesetzt haben, damit die neue Nachricht dann verarbeitet werden kann.

Nicht nur Verarbeitung: Wie wir aus Kafka Streams eine verteilte Datenbank erstellt haben und was dabei herausgekommen ist

Abbildung 4: Wir öffnen den Zugriff auf den Schlüsselwertspeicher für die Verarbeitungsmethode des Prozessors (danach muss jedes Skript, das mit Shared State arbeitet, die Methode implementieren). doProcess)

Option Nr. 2: Erstellen einer CRUD-API zusätzlich zu Kafka Streams

Nachdem wir unseren grundlegenden Aufgabenablauf festgelegt hatten, begannen wir zu versuchen, eine RESTful CRUD API für unsere Shared-State-Microservices zu schreiben. Wir wollten in der Lage sein, den Status einiger oder aller Objekte abzurufen sowie den Status eines Objekts festzulegen oder zu entfernen (nützlich für die Backend-Unterstützung).

Um alle Get State-APIs zu unterstützen, haben wir den Status jedes Mal, wenn wir ihn während der Verarbeitung neu berechnen mussten, lange Zeit in einem integrierten Schlüsselwertspeicher gespeichert. In diesem Fall ist es ganz einfach, eine solche API mithilfe einer einzelnen Instanz von Kafka Streams zu implementieren, wie in der folgenden Auflistung gezeigt:

Nicht nur Verarbeitung: Wie wir aus Kafka Streams eine verteilte Datenbank erstellt haben und was dabei herausgekommen ist

Abbildung 5: Verwenden des integrierten Schlüsselwertspeichers zum Abrufen des vorberechneten Zustands eines Objekts

Auch die Aktualisierung des Zustands eines Objekts über die API ist einfach zu implementieren. Im Grunde müssen Sie lediglich einen Kafka-Produzenten erstellen und damit einen Datensatz erstellen, der den neuen Status enthält. Dadurch wird sichergestellt, dass alle über die API generierten Nachrichten auf die gleiche Weise verarbeitet werden wie die von anderen Produzenten (z. B. Agenten) empfangenen Nachrichten.

Nicht nur Verarbeitung: Wie wir aus Kafka Streams eine verteilte Datenbank erstellt haben und was dabei herausgekommen ist

Abbildung 6: Mit dem Kafka-Produzenten können Sie den Zustand eines Objekts festlegen

Kleine Komplikation: Kafka hat viele Partitionen

Als Nächstes wollten wir die Verarbeitungslast verteilen und die Verfügbarkeit verbessern, indem wir pro Szenario einen Cluster von Microservices mit gemeinsam genutztem Status bereitstellen. Die Einrichtung war ein Kinderspiel: Nachdem wir alle Instanzen so konfiguriert hatten, dass sie unter derselben Anwendungs-ID (und denselben Bootstrap-Servern) laufen, wurde fast alles andere automatisch erledigt. Wir haben außerdem angegeben, dass jedes Quellthema aus mehreren Partitionen bestehen würde, sodass jeder Instanz eine Teilmenge solcher Partitionen zugewiesen werden könnte.

Ich möchte auch erwähnen, dass es üblich ist, eine Sicherungskopie des Statusspeichers zu erstellen, um diese Kopie beispielsweise im Falle einer Wiederherstellung nach einem Fehler auf eine andere Instanz zu übertragen. Für jeden Statusspeicher in Kafka Streams wird ein repliziertes Thema mit einem Änderungsprotokoll erstellt (das lokale Aktualisierungen verfolgt). Somit sichert Kafka ständig den Staatsspeicher. Daher kann im Falle eines Ausfalls der einen oder anderen Kafka Streams-Instanz der Statusspeicher schnell auf einer anderen Instanz wiederhergestellt werden, wo die entsprechenden Partitionen gespeichert werden. Unsere Tests haben gezeigt, dass dies in Sekundenschnelle erledigt ist, selbst wenn sich Millionen von Datensätzen im Laden befinden.

Beim Übergang von einem einzelnen Microservice mit gemeinsamem Status zu einem Cluster von Microservices wird die Implementierung der Get State API weniger trivial. In der neuen Situation enthält der Statusspeicher jedes Mikrodienstes nur einen Teil des Gesamtbildes (die Objekte, deren Schlüssel einer bestimmten Partition zugeordnet wurden). Wir mussten ermitteln, welche Instanz den Status des von uns benötigten Objekts enthielt, und taten dies basierend auf den Thread-Metadaten, wie unten gezeigt:

Nicht nur Verarbeitung: Wie wir aus Kafka Streams eine verteilte Datenbank erstellt haben und was dabei herausgekommen ist

Abbildung 7: Mithilfe von Stream-Metadaten bestimmen wir, von welcher Instanz aus der Status des gewünschten Objekts abgefragt werden soll. Ein ähnlicher Ansatz wurde mit der GET ALL API verwendet

Die wichtigsten Ergebnisse

Staatliche Stores in Kafka Streams können de facto als verteilte Datenbank dienen.

  • wird in Kafka ständig repliziert
  • Auf einem solchen System kann problemlos eine CRUD-API aufgebaut werden
  • Der Umgang mit mehreren Partitionen ist etwas komplizierter
  • Es ist auch möglich, der Streaming-Topologie einen oder mehrere Zustandsspeicher hinzuzufügen, um Hilfsdaten zu speichern. Diese Option kann verwendet werden für:
  • Langfristige Speicherung von Daten, die für Berechnungen während der Stream-Verarbeitung benötigt werden
  • Langfristige Speicherung von Daten, die bei der nächsten Bereitstellung der Streaming-Instanz nützlich sein können
  • viel mehr...

Aufgrund dieser und anderer Vorteile eignen sich Kafka Streams gut für die Aufrechterhaltung des globalen Zustands in einem verteilten System wie unserem. Kafka Streams hat sich in der Produktion als sehr zuverlässig erwiesen (seit der Bereitstellung hatten wir praktisch keinen Nachrichtenverlust), und wir sind zuversichtlich, dass seine Fähigkeiten damit nicht enden werden!

Source: habr.com

Kommentar hinzufügen