TL;DR

  • Reihenfolge der Ereignisse ist entscheidend: Für viele Anwendungen ist die Reihenfolge der Ereignisse nicht nur wichtig, sondern die Grundlage ihrer Logik.
  • Kafkas Garantie: Kafka garantiert die Reihenfolge der Nachrichten nur innerhalb einer einzigen Partition.
  • Die Lösung: Um sicherzustellen, dass zusammengehörige Ereignisse sequenziell verarbeitet werden, müssen Sie sie alle mit demselben Nachrichtenschlüssel (z. B. einer ride_id oder user_id) senden. Dies zwingt sie in dieselbe Partition.
  • Strengste Reihenfolge: Für die strengste Reihenfolge setzen Sie zusätzlich max.in.flight.requests.per.connection=1 auf Ihrem Producer, um zu verhindern, dass Wiederholungsversuche die Sequenz durcheinander bringen.
  • Reihenfolge ist nicht immer wünschenswert: Eine Reihenfolge zu erzwingen, wo sie nicht benötigt wird, kann die Leistung und Skalierbarkeit beeinträchtigen.

Einführung

Apache Kafka ist eine beliebte Wahl für den Aufbau von hochleistungsfähigen Echtzeit-Datenpipelines. Für einen breiteren, strategischen Überblick über die Plattform können Sie unseren Leitfaden zu Apache Kafka für technische Führungskräfte lesen. Wir streamen Ereignisse – Benutzeraktionen, Sensordaten, Datenbankänderungen – in Kafka mit dem impliziten Vertrauen, dass sie in der Reihenfolge verarbeitet werden, in der sie aufgetreten sind. Aber was ist, wenn das nicht der Fall ist?

Für viele Anwendungen ist die Reihenfolge der Ereignisse nicht nur wichtig, sondern die Grundlage ihrer Logik. Ein „Artikel zum Warenkorb hinzugefügt“-Ereignis muss vor einem „Kauf abgeschlossen“-Ereignis stattfinden. Ein „Spieler bewegt“-Ereignis muss vor einem „Spieler punktet“-Ereignis stattfinden. Wenn diese Reihenfolge durchbrochen wird, bekommen Sie nicht nur einen Bug, sondern Chaos.

Korrekte vs. inkorrekte Ereignisreihenfolge

Lassen Sie uns untersuchen, warum die Reihenfolge entscheidend ist, wie Kafka sie garantiert und wie leicht Sie diese Garantie brechen können, ohne es zu merken.

Die Anatomie einer Mitfahrgelegenheit: Eine Geschichte über die Reihenfolge

Stellen Sie sich eine einfache Mitfahr-Anwendung vor. Wenn Sie eine Fahrt buchen, wird eine Reihe von Ereignissen generiert, um deren Lebenszyklus zu verfolgen. Für eine einzelne Fahrt, sagen wir ride_id: ABC-123, ist die Reihenfolge nicht verhandelbar:

  1. RideRequested: Der Benutzer fordert eine Fahrt an.
  2. DriverAssigned: Ein Fahrer akzeptiert die Anfrage.
  3. RideInProgress: Der Fahrer holt den Benutzer ab und startet die Fahrt.
  4. RideCompleted: Der Fahrer setzt den Benutzer am Zielort ab.
  5. PaymentProcessed: Der Fahrpreis wird berechnet und abgebucht.

Wenn Ihre Backend-Systeme diese Ereignisse in der falschen Reihenfolge verarbeiten, brechen die Benutzererfahrung und Ihre Geschäftslogik zusammen. Stellen Sie sich vor, das PaymentProcessed-Ereignis kommt vor RideCompleted an. Ihr System könnte versuchen, dem Benutzer eine Fahrt in Rechnung zu stellen, die noch nicht beendet ist, oder die App-Benutzeroberfläche könnte verwirrenderweise „Fahrt abgeschlossen“ anzeigen, während der Benutzer noch im Auto sitzt. Dies ist ein klassischer Fall, in dem die Reihenfolge alles ist.

Kafkas goldene Regel der Reihenfolge

Hier ist das wichtigste Konzept, das man über die Reihenfolge in Kafka verstehen muss:

Kafka garantiert die Reihenfolge der Nachrichten nur innerhalb einer einzigen Partition.

Ein Kafka-Thema ist in eine oder mehrere Partitionen unterteilt, um Skalierbarkeit zu ermöglichen. Stellen Sie sich ein Thema als eine Kategorie für Nachrichten vor, wie „Benutzerklicks“ oder „Zahlungen“. Eine Partition ist ein kleineres, geordnetes Protokoll innerhalb dieses Themas. Durch die Aufteilung eines Themas auf mehrere Partitionen kann Kafka die Datenlast und die Verarbeitungsanfragen auf mehrere Server im Cluster verteilen. Dies ermöglicht es mehreren Consumern, parallel von einem Thema zu lesen, was den Durchsatz drastisch erhöht und dem System eine horizontale Skalierung ermöglicht. Während Nachrichten innerhalb einer Partition ein geordnetes, unveränderliches Protokoll sind, gibt es keine globale Garantie für die Reihenfolge über alle Partitionen eines Themas hinweg.1

Wie kommt es also, dass wir bei unserer Mitfahrgelegenheit Ereignisse in der falschen Reihenfolge haben? Das passiert, wenn Ereignisse für dieselbe Fahrt in verschiedenen Partitionen landen.

Visualisieren wir das. Hier sendet unser Producer Ereignisse für ride_id: ABC-123 ohne Schlüssel, und sie werden im Round-Robin-Verfahren auf die Partitionen des Themas verteilt.

Ereignisse in falscher Reihenfolge

Da ein Consumer von mehreren Partitionen gleichzeitig abruft, können Netzwerklatenz oder Verarbeitungsverzögerungen dazu führen, dass Ereignisse aus einer Partition nach Ereignissen aus einer anderen Partition verarbeitet werden, selbst wenn sie früher produziert wurden.

Der Consumer, der von mehreren Partitionen gleichzeitig liest, verarbeitet die Ereignisse in der Reihenfolge, in der sie von den Brokern eintreffen, was nun völlig durcheinander ist. Das Ergebnis ist ein fehlerhafter Anwendungszustand.

Die Lösung: Die Macht des Nachrichtenschlüssels

Um dies zu lösen, müssen wir sicherstellen, dass alle Ereignisse, die sich auf eine einzelne Fahrt beziehen, immer an dieselbe Partition gesendet werden. Dies erreichen wir durch die Verwendung eines Nachrichtenschlüssels.2

Wenn Sie eine Nachricht mit einem Schlüssel an Kafka produzieren, verteilt der Standard-Partitioner von Kafka sie nicht einfach zufällig. Stattdessen berechnet er einen Hash des Schlüssels und ordnet ihn einer bestimmten Partition zu.

Murmur2 Partitioner

Der Murmur23-Algorithmus sorgt für eine gleichmäßige Verteilung der Schlüssel auf die Partitionen.

Solange Sie denselben Schlüssel verwenden, landet Ihre Nachricht immer in derselben Partition.

Für unsere Mitfahr-App ist die Lösung einfach: Verwenden Sie immer die ride_id als Nachrichtenschlüssel für alle Ereignisse, die sich auf diese Fahrt beziehen.

Diese clientseitige Logik ist Teil dessen, was Kafka so leistungsfähig macht. Um mehr darüber zu erfahren, wie Clients Broker entdecken und Metadaten verwalten, lesen Sie unseren Deep Dive: Mehr als eine Adresse: Die verborgene Intelligenz von Kafka-Clients freischalten.

Wenn die Reihenfolge keine Rolle spielt (und wann sie schädlich ist)

Natürlich erfordern nicht alle Anwendungen eine strikte Reihenfolge. Stellen Sie sich vor, Sie sammeln Klickstream-Daten von einer Website. Jeder Klick ist ein unabhängiges Ereignis, und Sie sind wahrscheinlich an aggregierten Statistiken interessiert, wie z. B. der Anzahl der Klicks pro Seite. In diesem Fall könnte das Erzwingen aller Klicks für einen bestimmten Benutzer in eine einzige Partition zu „Hot Spots“ führen und Ihren Durchsatz begrenzen.

Für diesen Anwendungsfall würden Sie Nachrichten mit einem null-Schlüssel produzieren. Dies weist Kafka an, die Nachrichten im Round-Robin-Verfahren auf alle Partitionen zu verteilen, was genau das ist, was Sie zur Maximierung des Durchsatzes und zur Gewährleistung einer gleichmäßigen Last auf Ihren Brokern wollen.

Hier ist ein Diagramm, das dies veranschaulicht:

Ereignisse mit Null-Schlüssel

Hier wäre das Erzwingen einer Reihenfolge, wo sie nicht benötigt wird, ein Anti-Pattern, da es die Parallelität und Skalierbarkeit Ihres Systems unnötig einschränken würde.

Ein letzter „Fallstrick“: Producer-Wiederholungsversuche

Sie haben alles richtig gemacht. Sie verwenden die ride_id als Schlüssel. Aber Sie könnten trotzdem in seltenen Fällen Nachrichten in der falschen Reihenfolge sehen. Wie?

Stellen Sie sich diese Sequenz vor:

  1. Producer sendet RideInProgress. Die Anfrage schlägt mit einem temporären Netzwerkfehler fehl.
  2. Der Producer ist so konfiguriert, dass er es erneut versucht, aber bevor der Wiederholungsversuch stattfindet, sendet er die nächste Nachricht: RideCompleted. Diese ist erfolgreich.
  3. Der Wiederholungsversuch für RideInProgress wird nun gesendet und ist ebenfalls erfolgreich.

Die Reihenfolge der Nachrichten in der Partition ist jetzt RideCompleted, dann RideInProgress. Ihre Reihenfolge ist wieder gebrochen!

Producer-Wiederholungsversuche-Fallstrick

Für Anwendungen, die die strengste Reihenfolge erfordern, müssen Sie den Producer so konfigurieren, dass dies verhindert wird. Sie tun dies, indem Sie4 setzen:

max.in.flight.requests.per.connection=1

Diese Einstellung stellt sicher, dass der Producer nur einen Stapel von Nachrichten gleichzeitig an einen bestimmten Broker sendet und auf eine Bestätigung wartet, bevor er den nächsten sendet. Dies verhindert, dass eine spätere Nachricht eine fehlgeschlagene, aber wiederholte frühere Nachricht „überholt“. Der Kompromiss ist eine potenzielle Reduzierung des Durchsatzes, also verwenden Sie es, wenn die Reihenfolge eine nicht verhandelbare Anforderung ist.

Fazit

Kafka bietet eine leistungsstarke Grundlage für den Aufbau ereignisgesteuerter Systeme, aber seine Garantien sind mit Verantwortlichkeiten verbunden. Für eine große Anzahl von Anwendungen ist die Reihenfolge der Ereignisse das Fundament der Korrektheit. Indem Sie verstehen, dass die Reihenfolge von Kafka eine Garantie pro Partition ist, und indem Sie Nachrichtenschlüssel sorgfältig zur Steuerung der Datenplatzierung verwenden, können Sie zuverlässige, vorhersagbare und skalierbare Anwendungen erstellen.

Denken Sie immer daran: Wenn die Reihenfolge der Ereignisse eine Rolle spielt, kennen Sie Ihren Schlüssel und kennen Sie Ihre Partition.

Footnotes

  1. Konzepte und Begriffe - Apache Kafka, https://kafka.apache.org/documentation/#intro_concepts_and_terms

  2. Kafka-Nachrichtenschlüssel: Ein umfassender Leitfaden - Confluent, https://www.confluent.io/learn/kafka-message-key/

  3. MurmurHash - Wikipedia, https://en.wikipedia.org/wiki/MurmurHash

  4. Producer-Konfigurationen - Confluent-Dokumentation, https://docs.confluent.io/platform/current/installation/configuration/producer-configs.html#max-in-flight-requests-per-connection