Kafka Producer: acks, Retries und Idempotenz richtig
1. Übersicht
Wenn ein Producer eine Nachricht an Kafka sendet, müssen Sie drei Fragen beantworten: Wann gilt das Senden als erfolgreich, wie wird bei Fehlern wiederholt, und führt die Wiederholung zu doppelten Meldungen? Dieser Artikel erklärt acks, retries und enable.idempotence — drei Konfigurationen, die die Zuverlässigkeit und Korrektheit des Producers bestimmen. Nach der Lektüre wissen Sie, wie Sie einen produktionsreifen Producer konfigurieren und häufige Fehler vermeiden, die zu Datenverlust oder Duplikaten führen.
2. Erforderliche Abhängigkeiten
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>3.7.0</version>
</dependency>
Die Beispiele in diesem Artikel verwenden den reinen Kafka-Client (KafkaProducer), nicht über Spring Kafka, um klar zu zeigen, wie jede Konfiguration wirkt.
3. acks: Wann betrachtet der Producer die Zustellung als erfolgreich
acks bestimmt, wie viele Broker eine Bestätigung geben müssen, bevor der Producer die Nachricht als erfolgreich versendet ansieht.
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.ACKS_CONFIG, "all");
acks=0: Der Producer wartet auf keine Bestätigung. Am schnellsten, aber die Nachricht geht verloren, wenn der Broker ausfällt, nachdem er sie erhalten hat — ohne dass es bemerkt wird.acks=1: Nur das Leader-Partition gibt eine Bestätigung, nachdem es in das lokale Log geschrieben hat. Wenn der Leader ausfällt, bevor die Follower synchronisiert werden, geht die Nachricht verloren.acks=all(oder-1): Wartet auf Bestätigung von allen Repliken im ISR (in-sync replica set). Am sichersten, aber höhere Latenz.
Hinweis:
acks=allist wirklich nur sicher, wenn es mitmin.insync.replicas >= 2auf dem Broker kombiniert wird. Wennmin.insync.replicas=1, benötigtacks=allauch nur 1 Replik für die Bestätigung — nicht sicherer alsacks=1.
3.1. Broker-Konfiguration dafür
# server.properties oder Topic-Konfiguration
min.insync.replicas=2
Mit Replication Factor = 3 und min.insync.replicas=2 kann das System einen ausgefallenen Broker verkraften und weiterhin schreiben, während sichergestellt wird, dass die Daten auf mindestens 2 Kopien vorhanden sind, bevor eine Bestätigung gegeben wird.
4. retries: Behandlung von vorübergehenden Fehlern
Der Producer kann auf vorübergehende Fehler stoßen wie NotLeaderForPartitionException oder kurzfristige Netzwerkprobleme. retries ermöglicht dem Producer, automatisch wiederzusenden, ohne dass manuelles Code-Handling erforderlich ist.
props.put(ProducerConfig.RETRIES_CONFIG, Integer.MAX_VALUE);
props.put(ProducerConfig.RETRY_BACKOFF_MS_CONFIG, 100);
props.put(ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG, 120_000);
retries: maximale Anzahl der Wiederholungsversuche. Seit Kafka 2.1 sollte ein sehr hoher Wert (oderInteger.MAX_VALUE) gesetzt werden, unddelivery.timeout.mssollte zur Steuerung der Gesamtwartezeit verwendet werden, anstatt die Anzahl der Versuche zu zählen.retry.backoff.ms: Wartezeit zwischen Wiederholungsversuchen, um den Broker nicht zu überlasten.delivery.timeout.ms: Gesamtwartezeit vonsend()bis zum Erhalt des Ergebnisses (Erfolg oder endgültiger Fehler). Muss größer sein alsrequest.timeout.ms + linger.ms.
Problem: Ohne Vorsicht können Wiederholungen zu doppelten Nachrichten oder falscher Reihenfolge führen.
4.1. Warum Retries zu Duplikaten führen
1. Producer sendet Batch mit Nachricht A
2. Broker schreibt A erfolgreich, sendet ACK zurück
3. ACK geht auf dem Netzwerk verloren (Netzwerk-Timeout)
4. Producer erhält keine ACK -> behandelt als Fehler -> Retry
5. Broker erhält die Anfrage erneut -> schreibt A ein zweites Mal
=> Topic enthält 2 Kopien der Nachricht A
Dies ist eine klassische Situation: Der Broker hat erfolgreich geschrieben, aber der Producer weiß es nicht und sendet erneut. Idempotenz (siehe später) löst dieses Problem richtig.
4.2. Warum Retries zu falscher Reihenfolge führen
Wenn max.in.flight.requests.per.connection > 1, kann der Producer mehrere Batches parallel versenden. Batch 2 könnte vom Broker vor Batch 1 geschrieben werden, wenn Batch 1 einen Fehler hat und gerade wiederholt wird.
max.in.flight.requests.per.connection=5
Mit dieser Konfiguration ohne Idempotenz ist die Reihenfolge der Nachrichten in der Partition nicht garantiert, wenn Wiederholungen auftreten.
5. Idempotenz: Eliminierung von Duplikaten durch Wiederholung
enable.idempotence=true hilft dem Producer, automatisch zu vermeiden, dass Duplikate geschrieben werden, wenn Wiederholungen vorkommen, indem Kafka jedem Batch eine Producer ID (PID) und eine Sequenznummer zuordnet.
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
Wenn Idempotenz aktiviert ist, setzt Kafka automatisch die folgenden Werte (Sie müssen diese nicht selbst konfigurieren, sollten aber wissen, dass sie gesetzt werden):
acks = all
retries = Integer.MAX_VALUE (oder sehr hoch)
max.in.flight.requests.per.connection <= 5
5.1. Funktionsweise
Jede Producer-Session erhält vom Broker eine eindeutige PID. Jede Nachricht, die für eine Partition gesendet wird, hat eine Sequenznummer, die ständig zunimmt. Der Broker speichert die letzte Sequenznummer, die für jede PID + Partition geschrieben wurde.
Producer sendet Batch seq=5 für Partition 0
Broker: "Letzte Sequenznummer, die ich gespeichert habe, ist 4, 5 ist nächste -> schreiben, seq=5 speichern"
Producer sendet erneut (wegen Retry) Batch seq=5
Broker: "seq=5 habe ich bereits geschrieben -> ignorieren, ACK-Erfolg zurückgeben"
Dadurch erstellt eine Wiederholung kein Duplikat — der Broker erkennt und verwerft Duplikate automatisch anhand der Sequenznummer, ohne dass der Producer oder Consumer selbst etwas tun muss.
Hinweis: Idempotenz garantiert nur keine Duplikate in einer Producer-Session und innerhalb einer Partition. Wenn der Producer neu gestartet wird (neue PID), gilt die alte PID-basierte Deduplizierung nicht mehr. Für exactly-once über mehrere Partitionen/Topics hinweg benötigen Sie Transaktionen (
transactional.id) — das geht über reine Idempotenz hinaus.
5.2. Beispiel einer produktionsreifen Producer-Konfiguration
public ProducerFactory() {
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// Idempotenz aktivieren -> setzt acks=all, hohe Retries, in-flight <= 5 automatisch
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
props.put(ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG, 120_000);
this.producer = new KafkaProducer<>(props);
}
public void sendOrderEvent(String orderId, String payload) {
ProducerRecord<String, String> record =
new ProducerRecord<>("order-events", orderId, payload);
producer.send(record, (metadata, exception) -> {
if (exception != null) {
log.error("Senden der Bestellung {} fehlgeschlagen", orderId, exception);
} else {
log.info("Bestellung {} in Partition {} Offset {} geschrieben",
orderId, metadata.partition(), metadata.offset());
}
});
}
ENABLE_IDEMPOTENCE_CONFIG = trueist die wichtigste Zeile — sie setztacks=allautomatisch.- Der Callback in
send()teilt das tatsächliche Ergebnis mit: Wennexception != null, bedeutet dies, dass die Wiederholung bisdelivery.timeout.msfehlgeschlagen ist — jetzt müssen Sie die Geschäftslogik behandeln (Log, Alert, Fehler speichern).
6. Verhalten durch Tests überprüfen
Um die Deduplizierung zu illustrieren, implementieren wir einen einfachen MessageDeduplicator, der den Sequenznummern-Mechanismus auf Anwendungsebene nachahmt (verwendet, wenn Sie Idempotenz nicht aktivieren können, z. B. bei älteren Systemen):
public class MessageDeduplicator {
private final Map<String, Long> lastSeenSequence = new ConcurrentHashMap<>();
public boolean shouldWrite(String partitionKey, long sequence) {
Long last = lastSeenSequence.get(partitionKey);
if (last != null && sequence <= last) {
return false; // Duplikat, ignorieren
}
lastSeenSequence.put(partitionKey, sequence);
return true;
}
}
class MessageDeduplicatorTest {
@Test
void shouldSkipDuplicateSequence() {
MessageDeduplicator deduplicator = new MessageDeduplicator();
assertThat(deduplicator.shouldWrite("order-1", 5)).isTrue();
assertThat(deduplicator.shouldWrite("order-1", 5)).isFalse(); // Wiederholung mit gleicher seq
assertThat(deduplicator.shouldWrite("order-1", 6)).isTrue();
}
@Test
void shouldAllowDifferentPartitionKeysIndependently() {
MessageDeduplicator deduplicator = new MessageDeduplicator();
assertThat(deduplicator.shouldWrite("order-1", 1)).isTrue();
assertThat(deduplicator.shouldWrite("order-2", 1)).isTrue();
}
}
Der erste Test bestätigt: Gleiche Sequenznummer wird beim erneuten Versenden ignoriert — das ist echter Idempotenz. Der zweite Test bestätigt, dass die Deduplizierung nur im Bereich eines Keys/einer Partition gilt und andere Keys nicht beeinflusst.
7. Konfigurationsübersicht nach Anforderungen
| Anforderung | acks | enable.idempotence | Risiken |
|---|---|---|---|
| Log, Metriken nicht kritisch | 0 | false | Nachrichtenverlust bei Fehlern, aber höchster Durchsatz |
| Gleichgewicht zwischen Geschwindigkeit/Sicherheit | 1 | false | Nachrichtenverlust, wenn Leader ausfällt, bevor Follower sync |
| Kritische Geschäftsdaten (Bestellungen, Zahlungen) | all | true | Höhere Latenz, aber kein Verlust, keine Duplikate durch Retry |
8. Häufige Fehler
acks=allsetzen, abermin.insync.replicasvergessen: Man denkt, es ist sicher, aber der Broker benötigt nur 1 ISR zur ACK — nicht anders alsacks=1.- Eigene Retry-Logik parallel zur Standard-Retry des Kafka-Clients schreiben: Leicht zu doppeltem Versand führen, weil beide Retry-Schichten aktiv sind.
- Idempotenz aktivieren, aber
max.in.flight.requests.per.connection > 5setzen: Kafka lehnt diese Konfiguration beim Initialisieren des Producers ab (ConfigException), da es das durch Idempotenz unterstützte Limit übersteigt. - Idempotenz mit systemweit exactly-once verwechseln: Idempotenz schützt nur Producer-zu-Broker. Wenn Consumer Nachrichten verarbeitet und dann etwas macht (E-Mail senden, Geld abbuchen), das nicht idempotent ist, können Sie trotzdem doppelte Verarbeitung haben.
Hinweis:
enable.idempotence=truenicht aktivieren, wenn das System eine ältere Kafka-Version als 0.11 verwendet — das Feature wird vom Broker nicht unterstützt und der Producer schlägt beim Initialisieren fehl.
9. Fazit
acks, retries und enable.idempotence sind untrennbare Komponenten für einen zuverlässigen Producer. acks=all stellt sicher, dass Daten vor Bestätigung ausreichend repliziert werden; retries hilft, vorübergehende Fehler zu überwinden; und Idempotenz stellt sicher, dass Wiederholungen keine Duplikate erzeugen. Für kritische Geschäftsdaten sollten Sie standardmäßig alle drei zusammen verwenden — die Latenzkosten sind deutlich geringer als die Kosten für fehlerhafte oder doppelte Daten später.