Aus Linux-Magazin 12/2020

Events verarbeiten mit Apache Kafka

© Anatolii Stoiko, 123RF

Apache Kafka liest und schreibt nicht nur Events nahezu in Echtzeit, sondern lässt sich auch um Konnektoren erweitern, die Daten eines innerbetrieblichen Diensts für einen anderen aufbereiten. Der Artikel schaut der Software unter die Haube.

Event Streaming ist ein modernes Konzept, das darauf abzielt, große Datenmengen kontinuierlich zu verarbeiten. Oftmals tauchen synonym auch die Begriffe Stream Processing oder Streaming Analytics auf. Die Open-Source-Software Apache Kafka [1] hat sich dabei als De-facto-Standard für Event Streaming am Markt etabliert.

Entwickelt wurde das Publish-Subscribe-Messaging-System ursprünglich von der Karriereplattform Linked.in, um massive Datenmengen in Echtzeit zu verarbeiten. Heute ist Apache Kafka eine quelloffene Event-Streaming-Software, die laut Eigenauskunft des Projekts bei über 80 Prozent der Fortune-100-Unternehmen zum Einsatz kommt.

Dieser Artikel hilft dabei, sinnvolle Einsatzszenarien und Anwendungsfälle für Kafka kennenzulernen. Dazu taucht er in die Kafka-Architektur ein und beschreibt Kernkomponenten wie die Stream Processing Engine, lässt aber auch das Konnektor-Ökosystem nicht aus.

Als Event-Streaming-Plattform erfasst, verarbeitet, speichert und integriert Apache Kafka Daten in großem Maßstab. Die Software unterstützt zahlreiche Anwendungen, darunter verteiltes Protokollieren, Stream-Verarbeitung, Datenintegration und Pub-Sub-Messaging. Dabei verarbeitet Kafka die Daten kontinuierlich nahezu in Echtzeit, ohne sie vorher in eine eigene Datenbank zu schreiben.

Um uns einen vollständigen Überblick über Kafkas Funktionen zu verschaffen, gehen wir zunächst näher darauf ein, was eine Event-Streaming-Plattform ist und wie sie arbeitet. Dazu bietet es sich an, zunächst einmal zu erörtern, was ein Ereignis oder Event überhaupt ist. Das hilft bei der Erklärung, wie Kafka Ereignisse speichert, wie diese ins System hineingelangen und aus dem System herauskommen und wie sich Ereignisströme verarbeiten und analysieren lassen.

Als Ereignis gilt jede Art von Aktion, Vorfall oder Änderung, die eine Software oder Anwendung identifiziert oder aufzeichnet. Dabei kann es sich beispielsweise um eine Zahlung handeln, einen Mausklick auf die Website oder einen per Sensor erfassten Temperaturpunkt, jeweils zusammen mit einer Beschreibung des Geschehens.

Ein Ereignis verbindet also eine Benachrichtigung – ein zeitliches Element, auf dessen Basis das System eine andere Aktion auslösen kann – mit einem Zustand. Die Nachricht ist in den meisten Fällen ziemlich klein, üblicherweise einige Byte oder Kilobyte. Sie liegt gewöhnlich in einem strukturierten Format vor, etwa in JSON oder als ein mit Apache Avro oder Protocol Buffers serialisiertes Objekt.

Wer braucht Event Streaming?

Inzwischen setzen zahlreiche Organisationen auf der ganzen Welt Kafka ein; eine Gemeinschaft professioneller Programmierer entwickelt die Software ständig weiter. Aufgrund des hohen Datendurchsatzes, der Fehlertoleranz, Belastbarkeit und Skalierbarkeit von Kafka gibt es zahlreiche Anwendungsfälle in fast allen Branchen, vom Bankwesen über die Betrugserkennung bis hin zu Transport und IoT.

Konkrete Beispiele gibt es auch aus Deutschland: Die Deutsche Bahn verwendet Kafka, um Reisenden korrekte Informationen über den Status von Zugfahrten zu liefern – auf der Smartphone-App, im Zug selbst und auch auf dem Display im Bahnhof.

Bosch verwendet Kafka unter anderem für eine Track-and-Trace-App, die sowohl Werkzeuge und Maschinen als auch die Aktivitäten von Mitarbeitern auf Baustellen verfolgt und optimiert. Audi baute damit in den letzten Jahren eine Connected-Car-Infrastruktur auf. Der Versandhändler Zalando verwendet Kafka für sehr viele Ereignisse, die vom Online Shop über das Fulfillment bis hin zur Bezahlung reichen.

Die Liste an Kafka-Nutzern und den möglichen Einsatzszenarien ließe sich fortführen. Die folgenden Abschnitte stellen kurz die wichtigsten Einsatzzwecke vor.

Datenintegration

Kafka verbindet sich mit nahezu jeder anderen Datenquelle in traditionellen Unternehmensinformationssystemen, modernen Datenbanken oder in der Cloud. Es bildet dabei mit den für Kafka Connect verfügbaren Konnektoren einen effizienten Integrationspunkt, ohne dabei die Logik oder das Routing innerhalb der zentralisierten Infrastruktur zu verbergen.

Metriken und Überwachung

Kafka kommt häufig zum Überwachen von Betriebsdaten zum Einsatz. Dabei sammelt es Statistiken aus verteilten Anwendungen, um zentralisierte Feeds mit Echtzeitmetriken zu erzeugen.

Log-Aggregation

Moderne Systeme sind häufig verteilte Systeme. Um die anfallenden Daten der verschiedenen Komponenten zu protokollieren, muss das System sie an einem Ort bündeln. Kafka dient oft als einzige Quelle der Wahrheit, um Daten aus allen Quellen zu zentralisieren, unabhängig von Form oder Umfang.

Stream-Verarbeitung

Echtzeitberechnungen vorzunehmen und Ereignisströme zu bündeln, gehört zur Kernkompetenz von Kafka. Die Software nimmt Datenströme jeder Größenordnung entgegen (sowohl in Echtzeit als auch von anderen Schnittstellen wie Dateien oder Datenbanken), speichert und verarbeitet sie. Daher übergeben Unternehmen Kafka nicht nur technische Aufgaben wie das Transformieren, Filtern und Aggregieren von Daten, sondern setzen es auch für kritische Geschäftsanwendungen ein, etwa für Zahlungsvorgänge, Materialbestellung und ähnliches.

Publish-Subscribe-Messaging

Als ein verteiltes Pub/Sub-Messaging-System funktioniert Kafka gut als eine modernisierte Variante des traditionellen Message-Brokers. Kafka spielt seine Vorteile aus, wenn es darum geht, einen Prozess, der Ereignisse erzeugt, von einem oder mehreren anderen Prozessen zu entkoppeln, die Ereignisse empfangen. Dafür bietet sich Kafka als eine skalierbare und flexible Lösung an.

Architektur und Konzepte

Das Architekturbeispiel aus Abbildung 1 zeigt eine Sensoranalyse im IoT-Umfeld mit Kafka und liefert dabei einen guten Überblick über die einzelnen Kafka-Komponenten und deren Zusammenspiel mit anderen Technologien.

Abbildung 1: Kafka wertet hier die Messpunkte der IoT-Sensoren aus. Ein Kafka-Producer speist sie in die Plattform ein, ein Kafka-Consumer holt sie sich für das Monitoring. Zugleich landen sie über Kafka Connect in einer Spark-basierten Analyseplattform. Quelle: Confluent

Abbildung 1: Kafka wertet hier die Messpunkte der IoT-Sensoren aus. Ein Kafka-Producer speist sie in die Plattform ein, ein Kafka-Consumer holt sie sich für das Monitoring. Zugleich landen sie über Kafka Connect in einer Spark-basierten Analyseplattform. Quelle: Confluent

Kafkas Architektur fußt auf der abstrakten Idee eines verteilten Commit-Logs. Indem es das Log in Partitionen aufteilt, ist Kafka in der Lage, Systeme zu skalieren. Kafka modelliert Ereignisse dabei als Schlüssel-Wert-Paare (Key/Value).

Intern bestehen diese Schlüssel und Werte nur aus Byte-Sequenzen. Sie lassen sich aber in der Programmiersprache der Wahl oft als strukturierte Objekte im Typsystem dieser Sprache darstellen. Das Konvertieren zwischen Sprachtypen und internen Bytes heißt im Kafka-Jargon (De-)Serialisierung. Als serialisierte Formate kommen, wie oben erwähnt, meist JSON, JSON Schema, Avro oder Protobuf zum Einsatz.

Doch was genau bilden die Schlüssel und Werte ab? Werte stellen typischerweise ein Anwendungsdomänenobjekt oder irgendeine Form von roher Nachrichteneingabe in serialisierter Form dar – etwa die Ausgabe eines Sensors.

Als Schlüssel dürfen zwar auch komplexe Domänenobjekte herhalten, in der Regel bestehen sie aber aus primitiven Typen wie Zeichenketten oder Ganzzahlen. Der Schlüsselteil eines Kafka-Ereignisses identifiziert ein Ereignis nicht unbedingt eindeutig, wie es der Primärschlüssel einer Zeile in einer relationalen Datenbank täte. Er dient vielmehr zur Feststellung einer identifizierbaren Größe im System, zum Beispiel eines Benutzers, eines Auftrags oder eines bestimmten angeschlossenen Geräts.

Das klingt zunächst wenig bedeutsam. Tatsächlich entscheiden Schlüssel darüber, wie Kafka mit Dingen wie Parallelisierung und Datenlokalisierung umgeht, wie wir im Folgenden noch sehen werden.

Kafkas Topics

Ereignisse neigen dazu, sich zu häufen. Deshalb braucht die IT-Welt ein System, um sie zu organisieren. Kafkas grundlegendste Organisationseinheit ist das Topic, das in etwa einer Tabelle in einer relationalen Datenbank entspricht. Für Entwickler, die mit Kafka arbeiten, ist das Topic die Abstraktion, über die sie am meisten nachdenken. Sie erstellen Topics, um verschiedene Arten von Events zu speichern. Topics dürfen auch aus gefilterten und transformierten Versionen vorhandener Topics bestehen.

Ein Topic ist ein logisches Konstrukt. Die Events zu einem Topic speichert Kafka in einem Log. Solche Protokolle lassen sich leicht verstehen, da es sich um einfache Datenstrukturen mit bekannter Semantik handelt.

Drei Dinge sollten Sie dabei im Auge behalten. Erstens hängt Kafka Events stets an das Ende einer Protokolldatei an: Schreibt die Software eine neue Nachricht in ein Log, landet sie immer an letzter Position. Zweitens können Sie Events nur lesen, indem Sie eine willkürliche Position (Offset) im Log suchen und dann sequenziell Log-Einträge durchforsten. Abfragen wie bei ANSI SQL, mit denen Sie nach einem bestimmten Wert suchen, gestattet Kafka nicht. Drittens erweisen sich die Ereignisse im Log als unveränderlich – vergangene Geschehnisse lassen sich nur äußerst schwer ungeschehen machen.

Auch die Logs selbst sind im Grunde genommen Dinge von Dauer. Traditionelle Messaging-Systeme in Unternehmen kennen neben Topics auch Queues. Diese Warteschlangen puffern Nachrichten auf dem Weg von der Quelle zum Ziel. Allerdings löschen sie die Nachrichten nach dem Konsumieren in der Regel auch. Das Ziel besteht also nicht darin, die Nachrichten länger aufzubewahren, um sie später noch einmal an die gleiche oder eine andere Anwendung zu verfüttern (Abbildung 2).

Abbildung 2: Kafkas Consumer konsumieren die Daten mitunter in Echtzeit. Quelle: Quelle: Confluent

Abbildung 2: Kafkas Consumer konsumieren die Daten mitunter in Echtzeit. Quelle: Quelle: Confluent

Da Kafkas Topics als Log-Dateien vorliegen, sind die darin enthaltenen Daten von Natur aus nicht temporär wie bei traditionellen Messaging-Systemen, sondern dauerhaft verfügbar. Jedes Topic können Sie so konfigurieren, dass die Daten entweder nach Erreichen eines bestimmten Alters (Retention Time) ablaufen oder sobald das Topic eine bestimmte Größe erreicht hat. Die Zeitspanne darf sich von Sekunden bis hin zu Jahren erstrecken. Sie dürfen Nachrichten auch auf unbestimmte Zeit speichern.

Die Logs, die den Kafka-Topics zugrunde liegen, speichert das System als Dateien auf Festplatten. Schreibt Kafka ein Ereignis in ein Topic, ist es so dauerhaft wie Daten in einer klassischen relationalen Datenbank.

Die Einfachheit des Logs und die Unveränderlichkeit der darin enthaltenen Inhalte sind der Schlüssel zu Kafkas Erfolg als kritische Komponente in modernen Dateninfrastrukturen. Eine (wirkliche) Entkopplung der Systeme funktioniert dadurch deutlich besser als mit traditioneller Middleware (Abbildung 3), die auf den Extract-Transform-Load-Mechanismus (ETL) oder einen Enterprise Service Bus (ESB) setzt, und die entweder auf Web Services oder Message Queues basiert. Kafka vereinfacht das Domain-driven Design (DDD), erlaubt es aber auch, mit veralteten Schnittstellen zu kommunizieren [2].

Abbildung 3: Das Domain-driven Design von Apache Kafka hilft beim Entkoppeln der Middleware. Quelle: Quelle: Confluent

Abbildung 3: Das Domain-driven Design von Apache Kafka hilft beim Entkoppeln der Middleware. Quelle: Quelle: Confluent

Kafkas Partitionen

Müsste ein Topic ausschließlich auf einer Maschine leben, würde das die Skalierbarkeit von Kafka ziemlich radikal einschränken. Daher teilt die Software (im Gegensatz zu klassischen Messaging-Systemen) ein logisches Topic in Partitionen auf, die sie auf verschiedene Maschinen verteilt (Abbildung 4). Dadurch skalieren einzelne Topics beliebig, was den Datendurchsatz und die Lese- und Schreibzugriffe angeht.

Abbildung 4: Replikation in Kafka mit Leader, Followern, Topics und Partitions. Quelle: Quelle: Confluent

Abbildung 4: Replikation in Kafka mit Leader, Followern, Topics und Partitions. Quelle: Quelle: Confluent

Die Partitionierung nimmt das einzelne Topic und zerlegt es (ohne Redundanz) in mehrere Logs, von denen jedes auf einem separaten Knoten im Kafka-Cluster existiert. Das verteilt die Arbeit, Nachrichten zu speichern, neue Nachrichten zu schreiben und bestehende Nachrichten zu verarbeiten auf viele Knoten im Cluster.

Hat Kafka ein Topic partitioniert, braucht man eine Möglichkeit, um zu entscheiden, welche Nachrichten Kafka auf welche Partitionen schreiben soll. Taucht eine Nachricht ohne Schlüssel auf, verteilt Kafka die nachfolgenden Nachrichten in der Regel im Round-Robin-Verfahren auf alle Partitionen des Topics. In diesem Fall erhalten alle Partitionen einen gleichmäßigen Anteil der Daten, aber es fehlt eine Ordnung der Eingangsnachrichten.

Bringt die Nachricht einen Schlüssel mit, berechnet Kafka die Zielpartition aus einem Hash des Schlüssels. Auf diese Weise garantiert die Software, dass Nachrichten mit demselben Schlüssel immer in derselben Partition landen und daher immer in der richtigen Reihenfolge bleiben.

Wohlgemerkt: Die Schlüssel sind nicht eindeutig, sondern betreffen eine identifizierbare Größe im System. Gehören die Ereignisse zum Beispiel alle zum selben Kunden, garantiert die als Schlüssel eingesetzte Kunden-ID, dass alle Ereignisse eines bestimmten Kunden immer in der richtigen Reihenfolge eintrudeln. Diese garantierte Ordnung zählt zu den großen Vorteilen des Append-only-Commit-Logs in Kafka.

Kafka Broker

Bisher hat der Artikel Ereignisse, Topics und Partitionen erklärt, aber noch nicht allzu explizit die tatsächlichen Computer angesprochen. Aus Sicht der physischen Infrastruktur besteht Kafka aus einem Netzwerk von Maschinen, die Broker heißen. Heute handelt es sich dabei wohl weniger um separate physische Server, sondern in der Regel um Container, die auf Pods laufen, die wiederum auf virtualisierten Servern laufen, die auf tatsächlichen Prozessoren in einem physischen Rechenzentrum irgendwo auf der Welt laufen.

Was auch immer sie tun: Es handelt sich um unabhängige Maschinen, auf denen jeweils der Kafka-Broker-Prozess läuft. Jeder Broker hostet einen bestimmten Satz an Partitionen und bearbeitet eingehende Anfragen, um neue Ereignisse auf die Partitionen zu schreiben oder Ereignisse von dort zu lesen. Broker replizieren auch die Partitionen untereinander.

Replikation

Es würde nicht genügen, jede Partition auf nur einem Broker zu speichern: Unabhängig davon, ob es sich bei den Brokern um Bare-Metal-Server oder verwaltete Container handelt, sind sie und der ihnen zugrunde liegende Speicherplatz ausfallgefährdet. Aus diesem Grund kopiert Kafka die Partitionsdaten auf mehrere andere Broker, um sie sicher aufzubewahren.

Diese Kopien bezeichnet man als Follower-Replikate, während die Hauptpartition im Kafka-Jargon Leader-Replikat heißt. Erzeugt ein Producer Daten für den Leader (im Allgemeinen in Form von Lese- und Schreibvorgängen), arbeiten der Leader und die Follower zusammen, um diese neuen Schreibvorgänge für die Follower zu replizieren. Das geschieht automatisch. Der Entwickler kann darauf vertrauen, dass die Daten sicher sind, wenn ein Knoten im Cluster stirbt, weil ein anderer automatisch dessen Rolle übernimmt.

Client-Anwendungen

Nun verlässt der Artikel den Kafka-Cluster selbst und wendet sich den Anwendungen zu, die Kafka wahlweise befüllen oder anzapfen: den Producern und Consumern. Dabei handelt es sich um Client-Anwendungen, die den von den Kafka-Anwendern entwickelten Code enthalten, der dafür Sorge trägt, Nachrichten in Topics einzufügen und sie daraus zu lesen. Jede Komponente der Kafka-Plattform, die kein Kafka-Broker ist, gehört im Grunde genommen zu den Producern oder den Consumern – oder ist gar beides. Producer und Consumer bilden die Schnittstellen zum Kafka-Cluster.

Kafkas Producer

Die API-Oberfläche der Producer-Bibliothek ist ziemlich leichtgewichtig: In Java gibt es eine Klasse namens »KafkaProducer«, die den Client mit dem Cluster verbindet. Diese Klasse bekommt einige Konfigurationsparameter mit auf den Weg, einschließlich der Adresse einiger Broker im Cluster, einer geeigneten Sicherheitskonfiguration und anderer Einstellungen, die das Netzwerkverhalten des Producers beeinflussen.

Für den Entwickler transparent verwaltet die Bibliothek Verbindungs-Pools, Netzwerkpuffer, das Warten auf die Bestätigung von Nachrichten durch Broker und das unter Umständen erneute Übertragen von Nachrichten. Sie regelt auch eine Vielzahl anderer Details, um die sich kein Anwendungsprogrammierer kümmern muss. Das Beispiel aus Listing 1 zeigt den Einsatz der Kafka-Producer-API, um zehn Payment-Nachrichten zu erzeugen und zu versenden.

Listing 1

Kafka-Producer-API (Auszug)

[...]
try (KafkaProducerString,<Payment> producer = new KafkaProducer<String, Payment>(props)) {
  for (long i =**0; i <**10; i++) {
    final String orderId = "id" + Long.toString(i);
    final Payment payment = new Payment(orderId,**1000.00d);
    final ProducerRecord<String, Payment> record =
      new ProducerRecord<String, Payment>("transactions",
                              payment.getId().toString(),
                                                payment);
    producer.send(record);
  }
} catch (final InterruptedException e) {
    e.printStackTrace();
}
[...]

Kafkas Consumer

Wo es Produzenten gibt, warten meist auch Konsumenten. Der Einsatz der Consumer-API ähnelt im Prinzip der des Producers. Hier verbindet sich der Client über die Klasse »KafkaConsumer« mit dem Cluster. Dabei erhält die Klasse auch Konfigurationsoptionen, die unter anderem die Adresse des Clusters, Sicherheitsoptionen und andere Parameter festlegen. Auf Basis der Verbindung abonniert der Consumer dann ein oder mehrere Topics.

Kafka skaliert Consumer Groups mehr oder weniger automatisch. Genau wie »KafkaProducer« verwaltet auch »KafkaConsumer« das Verbindungs-Pooling und das Netzwerkprotokoll. Allerdings geht die Funktionalität auf der Consumer-Seite deutlich über die Netzwerkleitungen hinaus.

Liest ein Consumer eine Nachricht, löscht das diese nicht – das unterscheidet Kafka von den traditionellen Message Queues. Die Nachricht liegt weiterhin vor, jeder andere interessierte Consumer kann sie lesen.

Tatsächlich ist es in Kafka völlig normal, dass viele Consumer auf ein Topic zugreifen. Diese kleine Tatsache hat einen unverhältnismäßig großen Einfluss auf die Arten von Software-Architekturen, die um Kafka herum entstehen, denn Kafka eignet sich nicht nur für die Echtzeitdatenverarbeitung. Oft konsumieren auch andere Systeme die Daten, darunter Batch-Prozesse, Dateiverarbeitung, Request-Response-Webdienste (REST/SOAP), Data Warehouses oder Machine-Learning-Infrastrukturen.

Das Beispiel in Listing 2 zeigt auszugsweise, wie die Kafka-Consumer-API zehn Payment-Nachrichten konsumiert und verarbeitet.

Listing 2

Kafka-Consumer-API (Auszug)

[...]
try (final KafkaConsumer<String, Payment> consumer = new KafkaConsumer<>(props)) {
  consumer.subscribe(Collections.singletonList(TOPIC));
  while (true) {
    ConsumerRecords<String, Payment> records = consumer.poll(10);
    for (ConsumerRecord<String, Payment> record : records) {
      String key = record.key();
      Payment value = record.value();
      System.out.printf("key = %s, value = %s%n", key, value);
    }
  }
}
[...]

Kafkas Ökosystem

Gäbe es nur Broker, die partitionierte, replizierte Topics mit einer ständig wachsenden Sammlung von Producern und Consumern verwalten, die ihrerseits Events schreiben und lesen, wäre das bereits ein recht nützliches System.

Die Kafka-Entwickler-Community hat jedoch die Erfahrung gemacht, dass sich bei den Anwendern in der Praxis schon bald weitere Einsatzszenarien herauskristallisieren. Um diese umzusetzen, entwickeln die Anwender dann in der Regel immer wieder ähnliche Funktionen um den Kern von Kafka herum. Dazu bauen sie Schichten von Anwendungsfunktionalität auf, um bestimmte wiederkehrende Aufgaben zu bewältigen.

Der von den Kafka-Anwendern entwickelte Code mag dabei wichtige Arbeit leisten, ist aber meist nicht relevant für das eigentliche Geschäftsfeld, in dem diese tätig sind. Er schafft also höchstens indirekt Werte für die Anwender. Im Idealfall sollten daher die Kafka-Community oder die Infrastrukturanbieter solchen Code bereitstellen.

Das tun sie auch: Kafka Connect [3], die Confluent Schema Registry [4] Kafka Streams [5] und KsqlDB [6] sind Beispiele für diese Art von Infrastruktur-Code. Der Artikel sieht sich jedes dieser Beispiele der Reihe nach an.

Datenintegration mit Kafka Connect

Informationen stecken oft auch in anderen Systemen als Kafka. Manchmal möchte man Daten aus diesen Systemen in Kafka-Topics umwandeln, manchmal Daten aus Kafka-Topics in diese Systeme einspeichern. Als Integrations-API von Apache Kafka ist Kafka Connect das richtige Werkzeug für diesen Job.

Kafka Connect umfasst einerseits ein Ökosystem von Konnektoren und andererseits eine Client-Anwendung. Letztere läuft als Server-Prozess auf einer von den Kafka-Brokern separaten Hardware. Dabei handelt es sich nicht nur um einen einzelnen Connect-Worker, sondern um ein Cluster von Connect-Workern, die sich die Last des Datentransfers in und aus Kafka von und zu externen Systemen teilen. Das macht den Dienst skalierbar und fehlertolerant.

Kafka Connect enthebt den Benutzer auch davon, komplizierten Code schreiben zu müssen; eine JSON-Konfiguration genügt bereits. Listing 3 zeigt, wie sich Daten von Kafka in eine Elasticsearch-Installation streamen lassen.

Listing 3

JSON für Kafka Connect (Auszug)

[...]
{
  "connector.class": "io.confluent.connect.elasticsearch.ElasticsearchSinkConnector",
  "topics"         : "my_topic",
  "connection.url" : "http://elasticsearch:9200",
  "type.name"      : "_doc",
  "key.ignore"     : "true",
  "schema.ignore"  : "true"
}
[...]

Kafka Streams und KsqlDB

Bei einer sehr umfangreichen Kafka-basierten Anwendung neigen die Consumer dazu, die Komplexität zu erhöhen. Sie beginnen zum Beispiel mit einer einfachen zustandslosen Transformation. Dabei handelt es sich zum Beispiel um das Ausblenden von persönlich identifizierenden Informationen oder dem Ändern des Formats einer Nachricht, um den internen Schemaanforderungen zu entsprechen. Schon bald landen die Kafka-Anwender bei komplexen Aggregationen, Anreicherungen und mehr.

Der Code der Kafka-Consumer-API liefert nicht viel Unterstützung für solche Operationen: Die Entwickler der Kafka-Anwender müssen daher eine Menge Rahmencode programmieren, um mit Zeitfenstern, spät ankommenden Nachrichten, Nachschlagetabellen, Aggregationen nach Schlüsseln und mehr umzugehen.

Beim Programmieren gilt es zudem zu bedenken, dass Operationen wie Aggregation und Anreicherung typischerweise zustandsbehaftet sind. Diesen Zustand darf die Kafka-Anwendung nicht verlieren, doch zugleich muss sie hochverfügbar bleiben: Fällt die Anwendung aus, geht ihr Zustand ebenfalls verloren.

Man könnte versuchen, ein Schema zu entwickeln, um diesen Zustand irgendwo aufrechtzuerhalten. Doch so etwas ist teuflisch kompliziert zu schreiben und in großem Maßstab zu debuggen. Und es trüge nicht wirklich dazu bei, das Leben der Kafka-Anwender direkt zu verbessern. Aus diesem Grund bietet Apache Kafka eine API zur Stream-Verarbeitung an: Kafka Streams.

Kafka Streams

Die Java-API Kafka Streams erlaubt einen einfachen Zugriff auf alle rechnerischen Primitive der zustandslosen und zustandsbehafteten Stream-Verarbeitung. Dazu zählen Aktionen wie das Filtern, Gruppieren, Aggregieren sowie Zusammenführen von Daten und noch einiges mehr. Das erspart es, Framework-Code gegen die Consumer-API zu schreiben, um all diese Dinge zu erledigen. Kafka Streams unterstützt auch die potenziell großen Mengen an Zuständen, die sich aus den Berechnungen der Datenstromverarbeitung ergeben. Zudem hält es die Datensammlungen und -anreicherungen alternativ entweder im Arbeitsspeicher vor oder in einem lokalen Key-Value-Store (basierend auf RocksDB).

Zustandsbehaftete Datenverarbeitung und hohe Skalierbarkeit zu kombinieren, entpuppt sich als große Herausforderung. Die Streams-API löst beide Probleme: Sie behält einerseits den Status auf der lokalen Festplatte und auf internen Topics im Kafka-Cluster bei. Andererseits skaliert die Client-Anwendung (also der Kafka-Streams-Cluster) automatisch, wenn Kafka neue Client-Instanzen ergänzt oder entfernt.

Bei einem typischen Microservice führt die Anwendung die Datenstromverarbeitung zusätzlich zu anderen Funktionen aus. Beispielsweise kombiniert ein Versandhandel Sendungsereignisse mit Events in einem Änderungsprotokoll zu Produktinformationen. Letzteres enthält Kundendatensätze, um Sendungsbenachrichtigungsobjekte zu erzeugen, die andere Dienste dann in E-Mails und Textnachrichten umwandeln. Der Sendungsbenachrichtigungsdienst könnte jedoch auch verpflichtet sein, eine REST-API für synchrone Schlüsselabfragen durch mobile Anwendungen bereitzustellen, sobald die Apps Ansichten rendern, die den Status einer bestimmten Sendung anzeigen.

Der Dienst reagiert auf Ereignisse. In diesem Fall verbindet er zunächst drei Datenströme miteinander und führt vielleicht weitere zustandsbehaftete Berechnungen (State Windows) auf Basis der Zusammenführungen aus. Dennoch bedient der Service auch HTTP-Anfragen an seinem REST-Endpunkt.

Da es sich bei Kafka Streams um eine Java-Bibliothek handelt und nicht um einen Satz dedizierter Infrastrukturkomponenten, ist es trivial, das Stream Processing direkt in andere Anwendungen einzubinden und anspruchsvolle, skalierbare, fehlertolerante Stream-Verarbeitung zu entwickeln. Dies ist einer der Kernunterschiede zu anderen Stream-Processing-Frameworks wie KsqlDB, Apache Storm oder Apache Flink.

KsqlDB

Kafka Streams eignet sich als Java-basierte Stream-Processing-API sehr gut, um skalierbare, eigenständige Stream-Processing-Anwendungen zu erzeugen. Sie taugt aber auch dazu, die in Java-Anwendungen vorhandenen Funktionen zum Stream Processing anzureichern.

Was aber, wenn Anwendungen nicht in Java vorliegen oder die Entwickler eine einfachere Lösung suchen? Und was tun, wenn es aus architektonischer oder betrieblicher Sicht vorteilhaft erscheint, einen reinen Stream-Processing-Auftrag ohne eigene Webschnittstelle oder API zu implementieren, um dem Frontend die Ergebnisse zur Verfügung zu stellen? In diesem Fall kommt KsqlDB ins Spiel.

Die hochspezialisierte Datenbank ist auf Anwendungen optimiert, die Datenströme verarbeiten. Sie läuft auf einem eigenen skalierbaren, fehlertoleranten Cluster und stellt eine REST-Schnittstelle für Anwendungen bereit, die dann neue Stream-Verarbeitungsaufträge übermitteln, um diese auszuführen und Ergebnisse abzufragen.

Die Stream-Verarbeitungsjobs und Abfragen sind in SQL geschrieben. Dank der Schnittstellenoptionen via REST und Befehlszeile spielt es keine Rolle, welche Programmiersprache die Anwendungen verwenden. Es bietet sich an, im Entwicklungsmodus zu beginnen, entweder mit Docker, einem einzelnen Knoten, der nativ auf einem Entwicklungscomputer läuft, oder direkt in einem betreuten Service.

Zusammenfassend handelt es sich bei KsqlDB um eine eigenständige, SQL-gestützte Stream Processing Engine, die Ereignisströme kontinuierlich verarbeitet und die Ergebnisse datenbankähnlichen Anwendungen zur Verfügung stellt. Sie zielt darauf ab, ein mentales Modell für die meisten Kafka-basierten Stream-Processing-Workloads von Anwendungen bereitzustellen. Zum Vergleich: Listing 4 zeigt ein Beispiel für eine Logik, die kontinuierlich die Werte eines Attributs einer Nachricht sammelt und zählt. Der Anfang des Listings zeigt die Kafka-Streams-Variante, der zweite Teil die in KsqlDB geschriebene Version.

Listing 4

Kafka Streams versus KsqlDB

[...]
// Kafka Streams (Java):
builder
  .stream("input-stream",
        Consumed.with(Serdes.String(), Serdes.String()))
  .groupBy((key, value) -> value)
  .count()
  .toStream()
  .to("counts", Produced.with(Serdes.String(), Serdes.Long()));
// KsqlDB (SQL):
SELECT x, count(*) FROM stream GROUP BY x EMIT CHANGES;
[...]

Fazit und Ausblick

Kafka hat sich am Markt als De-facto-Standard für Event Streaming etabliert; sehr viele Firmen setzen es in diversen Projekten in Produktion ein. Zugleich entwickelt sich Kafka weiter.

Bei all den Vorteilen von Apache Kafka gilt es, auch die Nachteile nicht zu verschweigen: Es handelt sich beim Event Streaming um grundlegend neue Konzepte. Das Entwickeln, Testen und der Betrieb funktionieren daher komplett anders als bei bekannten Infrastrukturen. Beispielsweise gibt es Rolling Upgrades anstelle von Active-Passive-Deployments.

Dass es sich bei Kafka um ein verteiltes System handelt, wirkt sich auch auf den Produktivbetrieb aus. Kafka ist komplexer als reine Messaging-Systeme, und auch die Hardware-Anforderungen fallen ganz anders aus. Zum Beispiel erfordert Zookeeper stabile und geringe Latenzen. Dafür verarbeitet die Software große Datenmengen (oder auch kleine, aber kritische Geschäftstransaktionen) in Echtzeit und ist hochverfügbar. Dabei geht es nicht nur um das Senden von A nach B, sondern auch um lose gekoppelte und skalierbare Datenintegration von Quell- und Zielsystemen mit Kafka Connect und eine kontinuierliche Event-Verarbeitung (Stream Processing) mit Kafka Streams oder KsqlDB.

Der Artikel hat die grundlegenden Konzepte von Apache Kafka erläutert. Darüber hinaus existieren aber noch weitere hilfreiche Komponenten:

  • Der REST-Proxy [7] kümmert sich um die Kommunikation via HTTP(S) mit Kafka (Producer, Consumer, Administration).
  • Die Schema Registry [4] regelt die Data Governance. Sie verwaltet und versioniert Schemata und erzwingt bestimmte Datenstrukturen.
  • Cloud Services vereinfachen den Betrieb insbesondere komplett verwalteter Serverless-Infrastrukturen, aber auch den von PaaS-Angeboten.

Zugleich geht die Arbeit an Apache Kafka weiter. So wird Version 3.0 die Abhängigkeit von Zookeeper entfernen (für einfacheren Betrieb und noch bessere Skalierbarkeit und Performance), vollständig verwaltete (Serverless) Kafka Cloud Services anbieten und hybride Deployments (Edge, Datacenter und Multicloud) erlauben.

Einen Ausblick auf die Zukunft und einen Rückblick auf die Geschichte von Apache Kafka bieten auch zwei Videos vom diesjährigen Kafka Summit [8], einer alljährlichen Konferenz der Kafka-Community. Sie fand 2020 erstmals online statt und zählte über 30 000 registrierte Entwickler. (kki)

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 8 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben