В этой лекции мы углубимся в механизм передачи данных в реальном времени с Kafka, разберем, как работает модель публикации и подписки, а также рассмотрим различные гарантии доставки сообщений.
Публикация и подписка: как движутся данные между компонентами Kafka
Давайте начнем с основного принципа передачи данных: модели публикации и подписки (publish-subscribe). Kafka построена вокруг этой модели, которая позволяет продюсерам (отправителям) публиковать данные в топики, а консьюмерам (получателям) подписываться на эти топики и получать данные. Это похоже на электронную рассылку: вы отправляете письмо группе людей, а они читают его, когда у них есть время — только вместо писем у нас сообщения, а вместо группы людей — топики и консьюмеры.
Как это работает?
- Продюсер публикует сообщение: продюсер, как главный рассказчик в комнате, отправляет свои сообщения в определенный топик. Эти сообщения автоматически распределяются по партициям данного топика, чтобы обеспечить параллельную обработку.
- Брокеры хранят данные: Kafka-брокеры — это своеобразные хранилища, где сообщения остаются до тех пор, пока они не будут обработаны консьюмерами (или пока не истечет их срок хранения).
- Консьюмер считывает сообщение: консьюмер, как жадный слушатель, подписывается на топик (или даже конкретные партиции) и начинает считывать сообщения. Он отслеживает, какое сообщение он прочитал последним, используя offset (оффсет) — уникальный номер сообщения в топике.
Гарантии доставки: At Least Once, At Most Once, Exactly Once
Работа с передачей данных между системами всегда связана с вопросами надежности и консистентности. Kafka предлагает три уровня гарантии доставки сообщений:
1. At Least Once (Доставка как минимум один раз)
Основной принцип: сообщение может быть доставлено несколько раз, но никогда не будет потеряно.
Продюсер отправляет сообщение, и Kafka гарантирует, что оно будет записано в топик. Консьюмер может случайно обработать одно и то же сообщение дважды из-за сбоя при подтверждении обработки.
Пример: в интернет-магазине заказ на товар может быть случайно обработан дважды, и покупателю отправят две копии товара вместо одной.
2. At Most Once (Доставка максимум один раз)
Основной принцип: сообщение доставляется один раз, но может быть потеряно (например, в случае сбоя).
Продюсер отправляет сообщение, но если оно не будет подтверждено, Kafka допускает, что сообщение потеряется.
Пример: при обработке уведомлений о завершении задачи в системе мониторинга можно потерять одно уведомление, но важно избежать дублирования.
3. Exactly Once (Доставка ровно один раз)
Основной принцип: сообщение гарантированно доставляется ровно один раз.
Эта гарантия реализуется через сложные механизмы настройки Kafka и ретрансляции данных. Это наиболее сложный вариант, но он защищает от ошибок и повторной обработки.
Пример: подсчет денежных транзакций в банке, где важно не потерять ни одну запись и избежать повторной обработки.
Кстати, знали, что раньше "Exactly Once" была легендой или даже мифом в мире распределённых систем? Kafka сделала невозможное возможным и добавила эту гарантию, чем взбудоражила умы разработчиков.
Как выбрать подходящий уровень гарантии?
Ваш выбор зависит от бизнес-логики и требований вашей системы:
- Если потеря сообщений неприемлема (например, транзакции в банке), используйте Exactly Once.
- Если важнее избегать повторной обработки, чем потеря сообщения (например, уведомления), выбирайте At Most Once.
- Если допустима повторная обработка сообщений (например, аналитика), выбирайте At Least Once.
Как сообщение путешествует по Kafka: от отправки до получения
Давайте проследим путь сообщения от продюсера до консьюмера. Это как отследить доставку посылки — только намного быстрее!
1. Продюсер отправляет сообщение
Продюсер обращается к Kafka через драйвер (например, клиентскую библиотеку Kafka на Java). Каждое сообщение сопровождается метаданными, такими как ключ или временная метка, которые помогают распределять сообщения по партициям.
Пример кода продюсера на Java
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import java.util.Properties;
public class SimpleProducer {
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");
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
ProducerRecord<String, String> record = new ProducerRecord<>("my-topic", "key", "Hello Kafka!");
producer.send(record);
producer.close();
}
}
bootstrap.servers— адрес Kafka-брокера.key.serializerиvalue.serializer— сериалайзеры для превращения объектов в байтовую строку.
3. Сообщение записывается в топик
Сообщение "приземляется" в соответствующую партицию топика. Kafka сохраняет его в лог, а оффсет увеличивается.
4. Консьюмер считывает сообщение
Консьюмер обращается к Kafka посредством Consumer API. Он подписывается на топик и начинает считывать сообщения из соответствующих партиций. Kafka гарантирует, что оффсет будет указываться на последнее считанное сообщение для каждого консьюмера.
Пример кода консьюмера на Java
import org.apache.kafka.clients.consumer.Consumer;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import java.time.Duration;
import java.util.Collections;
import java.util.Properties;
public class SimpleConsumer {
public static void main(String[] args) {
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "my-consumer-group");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
Consumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Collections.singletonList("my-topic"));
while (true) {
for (ConsumerRecord<String, String> record : consumer.poll(Duration.ofMillis(100))) {
System.out.printf("Received message: %s, at offset: %d%n", record.value(), record.offset());
}
}
}
}
group.id— группа консьюмеров, которая позволяет распределять нагрузку между несколькими консьюмерами.poll(Duration)— метод для получения сообщений.
Типичные ошибки при передаче данных и их устранение
Иногда встречаются следующие проблемы:
- Дублирование сообщений: если консьюмер не успел подтвердить получение сообщения, Kafka может отправить его повторно. Это устраняется через модели обработки, такие как "Exactly Once".
- Потеря сообщений: если конфигурация продюсера настроена на "fire-and-forget", сообщения могут теряться при сбоях. Всегда настраивайте подтверждение записи (acks=all).
- Неравномерное распределение нагрузки среди партиций: если сообщения записываются без указания ключа, Kafka может распределить их хаотично. Установите ключ для сообщений, чтобы обеспечить равномерный баланс.
Зачем это нужно в реальной жизни?
Kafka отлично подходит для приложений, где важна работа с большими потоками данных в реальном времени. Например:
- Трекеры активности пользователей: анализ активности миллионов пользователей в реальном времени.
- Журналы и аналитика: сбор и анализ логов приложений.
- Онлайн-ставки и игры: обработка миллисекундных событий.
- Финансовые системы: обеспечение надежной доставки транзакций.
Вот так Kafka превращает данные в реальном времени в мощный инструмент для построения современных систем! Теперь вы знаете, как передавать данные, и готовы к следующим шагам: установке Kafka и созданию своих продюсеров и консьюмеров.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ