← Назад к вопросам

Что такое гарантия порядка в Kafka?

2.0 Middle🔥 181 комментариев
#Другое

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Гарантия порядка в Apache Kafka

Гарантия порядка в Kafka — это механизм, который гарантирует сохранение порядка сообщений при их отправке в очередь и потреблении. Это критично для систем, где последовательность событий важна для корректной обработки.

Ключевой концепт: Partition и Order

Kafka гарантирует порядок сообщений внутри одного раздела (partition). Все сообщения, отправленные в один partition, обрабатываются в том порядке, в котором они были получены.

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import java.util.Properties;

public class KafkaOrderExample {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        
        // Критично: acks=all для гарантии доставки
        props.put("acks", "all");
        // Одна копия в полёте в момент времени
        props.put("max.in.flight.requests.per.connection", "1");
        // Попытки переотправки
        props.put("retries", Integer.MAX_VALUE);
        
        KafkaProducer<String, String> producer = new KafkaProducer<>(props);
        
        // Все сообщения с одним ключом пойдут в ОДИН partition
        String userId = "user123";
        producer.send(new ProducerRecord<>("events", userId, "Event 1"));
        producer.send(new ProducerRecord<>("events", userId, "Event 2"));
        producer.send(new ProducerRecord<>("events", userId, "Event 3"));
        
        // Гарантия: потребитель получит Event 1, затем Event 2, затем Event 3
        
        producer.close();
    }
}

Три уровня гарантий доставки

1. At Most Once (максимум один раз)

Сообщение может быть потеряно, но не дублировано.

props.put("acks", "0"); // Producer не ждёт подтверждения
props.put("retries", "0"); // Нет переотправок

Использование: когда потеря данных не критична (логирование метрик).

2. At Least Once (минимум один раз)

Сообщение точно будет доставлено, но может быть дублировано.

props.put("acks", "1"); // Leader подтверждает
props.put("retries", "3"); // Переотправляем при ошибке
props.put("max.in.flight.requests.per.connection", "1"); // Один запрос в момент

Проблема: при переотправке возможно дублирование.

3. Exactly Once (ровно один раз)

Сообщение доставляется точно один раз. Самая строгая гарантия.

props.put("acks", "all"); // Все в синхронизации подтверждают
props.put("retries", Integer.MAX_VALUE);
props.put("max.in.flight.requests.per.connection", "1");
props.put("enable.idempotence", "true"); // ВАЖНО для exactly-once

Роль Partition Key

Ключ определяет, в какой partition пойдёт сообщение:

// Все события пользователя 123 пойдут в ОДИН partition
producer.send(new ProducerRecord<>(
    "user-events",  // topic
    "user123",      // KEY — определяет partition
    "payment made"  // value
));

// Все события пользователя 456 пойдут в ДРУГОЙ partition
producer.send(new ProducerRecord<>(
    "user-events",
    "user456",
    "profile updated"
));

// Сообщение без ключа распределяется случайно
producer.send(new ProducerRecord<>(
    "logs",
    null,  // Нет ключа
    "Application started"
));

Consumer Group и порядок

import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import java.time.Duration;

public class KafkaConsumerOrder {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");
        props.put("group.id", "order-processor");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        // Не переходить на следующее сообщение, пока не обработал текущее
        props.put("enable.auto.commit", "false");
        
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
        consumer.subscribe(java.util.Collections.singletonList("orders"));
        
        while (true) {
            ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
            
            for (var record : records) {
                // Обработка сообщений В ПОРЯДКЕ
                processOrder(record.value());
                // Только потом подтверждаем смещение (offset)
                consumer.commitSync();
            }
        }
    }
    
    private static void processOrder(String order) {
        System.out.println("Processing: " + order);
    }
}

Проблема: несколько партиций

Если данные распределены по нескольким partition, порядок между ними не гарантирован:

Partition 0: [Event1, Event3, Event5]
Partition 1: [Event2, Event4, Event6]

Потребитель может получить: Event2, Event1, Event4, Event3, Event6, Event5

Решение: если порядок критичен, используй один partition:

// Все события для одного пользователя в ОДИН partition
producer.send(new ProducerRecord<>(
    "user-timeline",
    "user123",  // KEY обеспечивает попадание в один partition
    "liked post"
));

Практический пример: обработка платежей

public class PaymentProcessor {
    private KafkaProducer<String, String> producer;
    
    public void publishPaymentEvent(String userId, String event) {
        ProducerRecord<String, String> record = new ProducerRecord<>(
            "payments",
            userId,  // ВАЖНО: гарантирует порядок событий одного пользователя
            event
        );
        
        producer.send(record, (metadata, exception) -> {
            if (exception != null) {
                // Обработка ошибки
                System.err.println("Failed: " + exception);
            } else {
                System.out.println("Partition: " + metadata.partition() + 
                                 ", Offset: " + metadata.offset());
            }
        });
    }
}

// Потребитель обрабатывает платежи в порядке
public class PaymentConsumer {
    public void consume() {
        // Гарантия: платежи одного пользователя обработаны в порядке
        // user123: [Deposit 100, Withdrawal 50, Deposit 30]
        // user456: [Payment 200, Refund 50, Payment 100]
    }
}

Когда порядок нарушается

Порядок нарушается, если:

  1. Нет ключа (key = null) — сообщения могут идти в разные partitions
  2. Несколько consumers в group — каждый потребляет свои partitions
  3. Сбой в обработке без retry — сообщение пропускается
  4. max.in.flight.requests > 1 — несколько запросов одновременно (при отказе)
// НЕПРАВИЛЬНО: может нарушить порядок
props.put("max.in.flight.requests.per.connection", "5");
// При отказе 2-го сообщения может переотправиться после 3-го

// ПРАВИЛЬНО: сохраняет порядок
props.put("max.in.flight.requests.per.connection", "1");

Заключение

Гарантия порядка в Kafka — это фундаментальное свойство для обработки последовательных событий. Ключевые моменты:

  • Порядок гарантирован внутри partition
  • Использование ключа обеспечивает попадание связанных сообщений в один partition
  • Для exactly-once нужны идемпотентные настройки
  • Порядок между partitions не гарантирован