Kafka – Nuts and Bolts

„In diesem Augenblick ging über die Brücke ein geradezu unendlicher Verkehr.“

Franz Kafka

Wer sich mit Micro Services und insbesondere mit dem Thema Event-Driven Microservice Architecture beschäftigt, wird um Apache Kafka nicht herumkommen. Bei Apache Kafka handelt es sich um ein überaus populäres Publish/Subscribe Messaging System, das bei vielen namhaften Firmen und Organisationen verwendet wird.

Eine Auswahl von Kafka Nutzern

Ein Messaging System transportiert Nachrichten von einem oder mehreren Publishern zu einen oder mehreren Subscribern.

Eine Event-Driven Microservice Architecture nutzt ein Messaging System um Nachrichten zwischen den Microservices auszutauschen. Der Vorteil bei dieser Architektur ist, dass die beteiligten Microservices sich untereinander nicht kennen müssen. Wie im Beitrag Domain Event Pattern bereits vorgestellt, kann eine Reihe von Microservices auf eine Nachricht reagieren, ohne dass der Sender der Nachricht Kenntnis von den Empfängern haben muss. Solch eine Entkopplung verringert nicht nur die Komplexität des Systems, sondern vereinfacht auch die Organisation der Entwicklungsteams, da auch ihr Kommunikationsbedarf sinkt.

Die Nachrichten bzw. Events die von Apache Kafka verwaltet werden sind jeweils einem Topic zugeordnet. Ein Topic ist ein Themenspeicher, der als Log organisiert ist. Das bedeutet das neue Nachrichten immer ans Ende des Logs angefügt werden. Häufig werden Topics auch mit Dateiverzeichnissen verglichen. Das ist nicht ganz abwegig, da Apache Kafka seine Topics im Dateisystem speichert.

Jedes Topic ist seinerseits in Partitionen unterteilt. Die Partitionierung hilft bei den Themen Skalierung und Ausfallsicherheit. Genau genommen ist nicht das Topic als Log organisiert, sondern die einzelnen Partitionen innerhalb des Topics.

In ein Topic werden die Nachrichten über die Producer geschrieben. Dabei sorgt Apache Kafka dafür, dass die Nachricht an eine der Partitionen angehängt wird und diese Änderung auch an die Replika der Partition weitergereicht wird. Wie die obige Abbildung zeigt, schreibt ein Producer nicht in eine feste Partition, sondern kann in jede Partition eines Topics schreiben. Auch können mehrere Producer in dasselbe Topic und auch in dieselben Partitionen schreiben.

Eine Besonderheit existiert bei dem Anhängen einer Nachricht an eine Partition. Nachrichten ohne einen Key werden gleichmäßig über die Partitionen verteilt. Nachrichten mit einem identischen Key landen immer in der gleichen Partition. Über den Key lässt sich damit die Reihenfolge zusammenhängender Nachrichten sicherstellen. Beispielsweise sollten Nachrichten über die Veränderungen an einem Warenkorb nicht in zufälliger Reihenfolge verschickt werden.

Die Nachrichten können von einem Consumer aus einem Topic gelesen werden. Dabei gehört jeder Consumer einer Consumer Group an.

Im einfachsten Fall existiert nur ein Consumer und er liest Nachrichten reihum von allen Partitionen des Topics. Im Gegensatz zu anderen Messaging Systemen wird eine Nachricht aber nicht sofort gelöscht, nachdem sie konsumiert wurde. Für den Consumer wird nur ein Offset innerhalb der Partition inkrementiert. Ob und wann die Nachricht gelöscht wird ist konfigurierbar. Die Lebensdauer einer Nachricht beträgt standardmäßig sieben Tage.

Andere Consumer, die einer anderen Consumer Group angehören, konsumieren die gleichen Nachrichten und besitzen einen eigenen Offset für die Partitionen. Fällt ein Consumer aus, dann kann er nach einem Neustart an seinem vorherigen Offset die Verarbeitung wieder aufnehmen.

Befinden sich mehrere Consumer in einer Consumer Group, dann liest jeder Consumer nur aus einem Teil der Partitionen. In diesem Fall liest also der erste Consumer aus den ersten beiden Partitionen und der zweite Consumer aus den restlichen beiden Partitionen. Eine einfache aber effektive Technik um einen einzelnen Consumer zu entlasten, wenn mehr Nachrichten eingehen, als er abarbeiten kann. Diese Lösung skaliert in diesem Beispiel bis zu vier Consumer, von denen dann jeder eine einzelne Partition liest. Sollen noch mehr Consumer verwendet werden, dann muss die Anzahl der Partitionen erhöht werden.

Die Consumer können dynamisch der Gruppe hinzugefügt oder entnommen werden, die Gruppe ändert dann automatisch, welcher Consumer welche Partitionen liest. Dies ist nicht nur hilfreich für das Abfangen von Spitzen in der Verarbeitung, sondern natürlich auch im Fehlerfall. Fällt einer der Consumer aus, übernehmen die anderen Consumer seine Partitionen. Kehr der Consumer zurück in die Gruppe, dann geben seine Kollegen ihn seine Partitionen zurück.

Nach diesem kurzen Einblick in die grundsätzliche Arbeitsweise von Apache Kafka wird das Thema des nächsten Beitrags die Einbindung in eine Spring Boot Anwendung sein.