In einem verteilten System müssen wir immer davon ausgehen, dass einzelne Komponenten ausfallen.
Es ist in verteilten Systemen im Allgemeinen nicht möglich, eine Nachricht exakt einmal zu senden.
Es ist relativ einfach, eine Nachricht höchstens einmal zuzustellen. In Kafka setzen wir dafür acks=0 im Producer.
Der Producer sendet die Nachricht und wartet nicht auf eine Bestätigung. Ob die Nachricht ankommt oder nicht, ist in diesem Modus egal. Wenn sie ankommt, dann genau einmal. Wenn nicht, wird nicht erneut versucht. Also kommt sie höchstens einmal an.
At-least-once ist ebenfalls relativ einfach umzusetzen. Wir setzen acks=all im Producer. Der Producer sendet eine Nachricht und wartet auf die Bestätigung des Leaders. Bevor der Leader bestätigt, wartet er darauf, dass alle In-Sync Replicas die Nachricht erhalten haben. Wenn der Producer die Bestätigung erhält, weiß er, dass die Nachricht angekommen ist und unter vernünftigen Annahmen auch lesbar bleibt, selbst wenn der Leader ausfällt.
Wir setzen min.insync.replicas=2, um zu vermeiden, dass nur noch ein Broker bestätigt und der Producer trotzdem weiterschreibt. Schlägt das Acknowledgement fehl, versucht der Producer, die Nachricht erneut zu senden. Dadurch können Nachrichten mehrfach ankommen.
Exactly-once ist etwas anspruchsvoller. Wir können Nachrichten nicht einfach exakt einmal senden, da das Netzwerk jederzeit ausfallen kann. Dennoch erreichen wir auf Producer-Seite mit acks=all, min.insync.replicas=2 und enable.idempotence=true eine Exactly-Once-Semantik - solange derselbe Producer aktiv ist.
Wie funktioniert das genau? Der Producer sendet in jeder Nachricht eine Sequenznummer mit. Sie beginnt bei 0 und wird mit jeder Nachricht inkrementiert. Wenn der Leader die Sequenznummer 2 vor der Sequenznummer 1 erhält, erkennt er die Lücke und lehnt die 2 zunächst ab. Der Producer sendet dann sowohl 1 als auch 2 erneut. Wenn die Nachrichten in der richtigen Reihenfolge ankommen, werden sie nach der Replikation bestätigt.
Was passiert, wenn der Producer das Ack nicht erhält? Er sendet die Nachricht erneut. Da der Leader dieselbe Sequenznummer bereits verarbeitet hat, verwirft er das Duplikat und bestätigt nur noch.
Mit Idempotenz stellen wir also sicher, dass eine Nachricht pro Producer-Session nicht doppelt geschrieben wird und die Reihenfolge erhalten bleibt. Aber nur, solange der Producer lebt und nicht in den Timeout delivery.timeout.ms läuft.
Reicht das schon für End-to-End-Exactly-Once? Noch nicht.
Idempotenz löst nur das Duplikatproblem beim Schreiben durch einen Producer. Für eine Read-Process-Write-Pipeline brauchst du zusätzlich Transaktionen.