Apache Kafka als Backend für Webanwendungen?

Kafka wird dort eingesetzt, wo viele Daten in kurzer Zeit anfallen und verarbeitet werden sollen. Also auch im Backend für Webanwendungen?

Werkzeuge  –  2 Kommentare
Apache Kafka als Backend für Webanwendungen?

Üblicherweise kommt Kafka zum Einsatz, wenn es darum geht, massenhaft Daten von vielen Systemen zu sammeln und auszuwerten, beispielsweise Log-Einträge, Zustandsinformationen oder Fehler-Events. Es dagegen als Backend für Webanwendungen zu benutzen, klingt wie eine dumme Idee. Das ist es eigentlich auch – eine, die beim Gespräch mit Kollegen an der Kaffeemaschine aufkam. Sie blieb aber irgendwie hängen, und damit auch die Frage, ob es überhaupt möglich ist und ob man dabei nicht die Vorteile von Kafka für sich nutzen kann.

Inwiefern ist solch ein Einsatzzweck überhaupt denkbar? Kafka trennt lesende Zugriffe durch sogenannte Consumer vom Erzeugen neuer Nachrichten in Form von Producern. Durch diese lose Kupplung können beide Seite unabhängig bedient und auch skaliert werden. Da der Inhalt der verteilten Nachrichten für Kafka grundsätzlich egal ist, müssen die ausgetauschten Daten nur in ein entsprechendes Format gebracht werden, das sie später wieder verwertbar macht. Die Idee wäre, jede Benutzeraktion als Event abzubilden und mit anderen Clients auszutauschen. Durch die Art wie Kafka Informationen bekommt und verteilt, sollten theoretisch beliebig viele Clients damit arbeiten können. Da Kafka sich ebenfalls um die Persistenz der Nachrichten kümmert, sollte ein Client nach einem Crash oder bei einer späteren Anmeldung da weiter machen können, wo er aufgehört hat und sogar mitbekommen, was zwischendurch passiert ist. Einziger Nachteil: Die Persistenz ist von begrenzter Dauer. In der Theorie sollte die Idee jedenfalls umsetzbar sein. Ein Proof of Concept soll den Beweis erbringen.

Kafka ist ein verteiltes Nachrichtensystem und zeichnet sich durch hohe Skalierbarkeit und Redundanz aus, da es sich auf mehrere Instanzen verteilen kann. Dadurch ist es hoch verfügbar und gewappnet gegen den Ausfall einzelner Instanzen, zwischen denen sich Nachrichten synchronisieren. Allerdings gibt es keine Garantie, dass beim Crash einer Instanz alle Nachrichten synchronisiert sind. Datenverlust ist folglich nicht ganz ausgeschlossen, aber eher unwahrscheinlich.

Zookeeper, ein weiteres System, das mit Kafka ausgeliefert wird, startet ausgefallene Instanzen neu oder tauscht sie durch Ersatzinstanzen. Durch die Verteilung über mehrere Instanzen verkraftet Kafka eine große Zahl von Consumern, die sich ad-hoc oder permanent per Publish-Subscribe-Mechanismus verbinden. Nachrichten sammelt Kafka unter sogenannten Topics, die sich zwecks Clustering noch auf sogenannte Partitionen verteilen können.

Um neue Nachrichten zu kreieren, erstellt man Producer. Consumer sind notwendig, um Nachrichten lesen und verarbeitet zu können. Kafka kann ebenfalls Nachrichten verarbeiten und daraus neue Nachrichten generieren. Das erfolgt über Stream-Prozessoren. Die Nachrichten bestehen aus einem Zeitstempel, dem Topic und dem Inhalt, der beliebig ist und von einfachen Strings über JSON bis hin zu kompletten (serialisierten) Java-Klassen reichen kann. Kafka versieht eingegangene Nachrichten automatisch mit einem Offset und damit einer fortlaufenden Reihenfolge. Dadurch entsteht eine Art Log, in dem alle Nachrichten zeitlich geordnete Events beziehungsweise Zustände darstellen. Außerdem kümmert sich Kafka um Housekeeping, indem es je nach Konfiguration alte Nachrichten verwirft. Dabei greift eine sogenannte Retention Time, die bestimmt, ob die Nachrichten nur wenige Sekunden, Tage oder auch nahezu ewig erhalten bleiben. Der zur Verfügung stehende Speicherplatz ist natürlich ein entscheidender Faktor, sowie die Menge und Größe der anfallenden Nachrichten.

Das soll als grober Überblick zu Kafka reichen. Üblicherweise wird Kafka eben genutzt, um Informationen von verschiedenen Systemen wie Log-Einträge, aktuelle Zustände von IoT-Geräten, Tracking-Informationen und ähnliches zu sammeln und möglichst in Echtzeit zu verarbeiten und zu analysieren. Big Data und Machine Learning sind hier die Schlagworte. Einige Anwendungsfälle sehen auch vor, diese Daten in einer Oberfläche wie einem Dashboard anzuzeigen.

Inwiefern kann man dies nun als Backend für eine Webanwendung benutzen? Dafür muss man zunächst ein paar Annahmen über die Applikation treffen. Zum einen sollte jede Benutzerinteraktion als ein Event dargestellt werden können, die in ihrer Summe den Gesamtzustand der Anwendung repräsentieren. Des Weiteren sollte es möglich sein, dass diese in ihrer zeitlichen Abfolge geordnet ablaufen können, sodass sich theoretisch das Wiederherstellen älterer Zustände oder Replay-Möglichkeiten ergeben. Mehrere Benutzer sollten darüber hinaus auf demselben Zustand arbeiten können, ohne sich mit konkurrierenden Änderungen gegenseitig ins Gehege zu kommen.

Stellt man sich zum Beispiel einen einfachen Chatclient vor, fällt der Vergleich nicht schwer. Es gibt mehrere Benutzer, die Textnachrichten verfassen. Sie erstellen fortlaufend immer neue Nachrichten, und löschen oder ändern keine alten Nachrichten. Diese Eingaben dienen als Basis für das Generieren und Publizieren entsprechender Kafka-Nachrichten. Man könnte auch einzelne Chaträume als eigene Topics modellieren. Andere Clients brauchen sich im Gegenzug nur auf das jeweilige Topic einzuschreiben und bekommen so Nachrichten anderer Benutzer zu lesen. Dass rein technisch gesehen Nachrichten nicht in Echtzeit ankommen müssen und mal ein kleiner Zeitversatz entstehen kann, ist dabei nicht schlimm. Da jeder Consumer seinen Lesefortschritt sowie -richtung der Nachrichten selbst verwaltet, ist das Wegbrechen einer Instanz nicht fatal und er fängt einfach später wieder da an, wo er aufgehört hat. Zwischendurch eingehende Nachrichten verarbeitet Kafka ebenso mit, ähnlich wie bei Messengern wie Whatsapp, wenn man eine Zeit lang offline war. Auch dass eventuell ganz alte Nachrichten, die länger als die konfigurierte Retention zurück liegen, nicht mehr vorhanden sein können, ist bei einem Chat nicht ganz so tragisch, da es ja um die zeitnahe direkte Kommunikation geht. Im Zweifelfall merkt sich der Client die lokale Historie, und es geht eher um die Überbrückung von Ausfallzeiten.

In diesem simplen Anwendungsbeispiel fungiert Kafka als zentraler Nachrichtenverteiler sowie als Persistenzschicht. Entwickler müssen theoretisch nur eine Client-App erstellen und die Anwendung ist fertig. In der Praxis ist es aus Sicherheitsgründen sinnvoll, noch eine weitere Schicht als Middleware zwischen Clients und Kafka einzubinden. Da Kafka einfach jegliche eingehenden Nachrichten entgegennimmt und in seinen Log schreibt, wäre es ziemlich anfällig für DDoS-Angriffe und auch Datenmüll wäre sofort persistent. Da Kafka jedoch keine Poison Message oder Dead Letter Queues wie JMS kennt, ist weniger tragisch. Dennoch bietet es sich an, die an Kafka gegebenen Nachrichten durch eine Middleware zu moderieren.

Neben der grundsätzlichen Umsetzbarkeit bietet dieser Ansatz auf den ersten Blick ein paar zusätzliche Vorteile. Durch das entkoppelte Schreiben (Producer) und Lesen (Consumer) erfolgt die Verarbeitung von Ende zu Ende komplett asynchron beziehungsweise Event-getrieben, sodass kein Client blockiert ist, oder auf das Ergebnis einer Nachricht warten muss. Da Kafka ein eigenes System ist und es Bibliotheken für verschiedene Programmiersprachen gibt, können Anwender verschiedene Clients auch mit unterschiedlichen Programmiersprachen schreiben. Als zusätzliche Option bietet sich auch die Nutzung eines Kafka REST Proxy an.

Im Folgenden wird ein vorhandenes kleines Chat-Frontend als React-App mit einer simplen Middleware auf Node-Basis angebunden. Die Kommunikation zum Frontend geschieht per Websocket. In die andere Richtung wird Kafka direkt angebunden. Das gesamte Beispiel ist im GitHub-Repository des Autors zu finden.