Брокеры сообщений

Брокеры сообщений #

1. Плюсы и минусы брокеров сообщений #

Плюсы:

  • Асинхронность: Службы могут работать независимо друг от друга.
  • Масштабируемость: Легко увеличивать количество потребителей.
  • Надежность: Сообщения не теряются благодаря механизмам повторных попыток и подтверждений.
  • Разделение нагрузки: Потребители могут обрабатывать сообщения параллельно.

Минусы:

  • Сложность настройки: Требует правильной конфигурации для эффективной работы.
  • Задержка: Добавление брокера увеличивает задержку доставки сообщений.
  • Поддержка инфраструктуры: Нужны усилия для мониторинга и администрирования.

2. Разница между Kafka и RabbitMQ #

  • Модели сообщений

    • Kafka. Сообщения не удаляются после доставки
    • RabbitMQ. Сообщения удаляются после доставки
  • Обработка сообщений

    • Kafka. Потребители сами запрашивают сообщения из топиков, читая их по мере необходимости
    • RabbitMQ. Сообщения пушатся к потребителям из очереди, когда те готовы их принять (следовательно сервер может лечь, если данных данных слишком много)
  • Производительность

    • Kafka быстрее и производительнее чем RabbitMQ (за счет partition-ов + горизонтальное масштабирование)

3. Что такое топик в Kafka? #

Топик - это канал передачи информации определенного типа. Каждый топик в свою очередь разделен на partition-ы


На что делится топик?

В Apache Kafka топик (topic) делится на разделы (partitions). Разделение топика на разделы позволяет распределять сообщения между брокерами, обрабатывать их параллельно и обеспечивать масштабируемость и отказоустойчивость.


4. Что такое Producer и что такое Consumer? #

  • Producer - компонент, отправляющий сообщения (данные) в Kafka
  • Consumer - компонент, получающий сообщения из Kafka

Что даст увеличение количества консьюмеров?

Увеличение количества потребителей (consumers) в группе потребителей (consumer group) может повлиять на производительность и обработку данных, но с определенными ограничениями:

  1. Параллельная обработка:

    • Увеличение числа потребителей в группе позволяет обрабатывать данные параллельно, так как каждый потребитель обрабатывает свои разделы (partitions).
    • Если количество потребителей меньше или равно количеству разделов, это приводит к увеличению производительности.
  2. Ограничения:

    • Один раздел может быть назначен только одному потребителю в группе.
    • Если количество потребителей превышает количество разделов, “лишние” потребители не будут получать данные, так как разделы не могут быть разделены между несколькими потребителями.
  3. Пример:

    • Топик с 4 разделами:
      • Если в группе 2 потребителя, то каждый будет обрабатывать 2 раздела.
      • Если в группе 4 потребителя, каждый получит 1 раздел.
      • Если в группе 5 потребителей, один из них останется без работы.

5. Что такое partition в Kafka? #

Это подразделение топика, в котором хранятся сообщения. По default-у сообщения будут закидываться в разные partition-ы, что увеличивает скорость обмена сообщениями между микросервисами, но при этом нет гарантии очередности доставки сообщений. Если нам надо, что бы какие-то сообщения приходили в строгой очередности - для этого существует ключ partition-ов, который позволяет объединить сообщения в очередь в конкретном partition-е


На что делится partition?

Раздел (partition) не делится дальше, он является минимальной единицей хранения и обработки данных в Kafka. Однако внутри раздела данные организуются следующим образом:

  1. Лог записи:

    • Сообщения в разделе хранятся в лог-файле и имеют уникальные смещения (offsets), которые используются для идентификации и упорядочивания сообщений.
  2. Реплики:

    • Каждый раздел может иметь одну или несколько копий (реплик) для обеспечения отказоустойчивости.
  3. Лидер и фолловеры:

    • Один брокер управляет разделом как лидер, а остальные брокеры хранят реплики и выступают как фолловеры.

Как сообщения распределяются по партициям?

Производитель (producer) определяет, в какой раздел отправить сообщение, используя следующие подходы:

  1. Без ключа (Round-Robin):

    • Если ключ не указан, сообщения равномерно распределяются по всем доступным разделам.

    • Пример:

      Partition 0: Msg1, Msg4
      Partition 1: Msg2, Msg5
      Partition 2: Msg3, Msg6
      
  2. С использованием ключа (Key-based partitioning):

    • Если сообщение содержит ключ, Kafka использует хеширование ключа для выбора раздела:

      partition = hash(key) % number_of_partitions
      
    • Это гарантирует, что все сообщения с одинаковым ключом будут попадать в один раздел.

  3. Кастомный алгоритм (Custom Partitioning):

    • Производитель может реализовать собственную логику распределения сообщений по разделам, например, на основе содержимого сообщения.
  4. Пример с ключами:

    • Топик с 3 разделами:

      Key: "user1" -> Partition 0
      Key: "user2" -> Partition 1
      Key: "user3" -> Partition 2
      
  5. Manual Partitioning:

    • Производитель может явно указать, в какой раздел отправить сообщение.

6. Гарантии доставки в Kafka #

  • At least once. Гарантия того, что сообщение будет доставлено по крайней мере один раз, либо больше.
  • At most once. Гарантия того, что сообщение будет доставлено максимум один раз, либо не будет доставлено вовсе
  • Exactly once. Гарантия того, что сообщение будет доставлено ровно один раз. Это наиболее строгий уровень гарантии и требует согласованности между producer-ом и consumer-ом, чтобы избежать дублирования сообщений

7. Какие существуют паттерны работы с очередями? #

  • Point-to-Point (P2P): Один продюсер, один потребитель. Сообщение читается одним потребителем и удаляется из очереди.
  • Publish-Subscribe (Pub/Sub): Один продюсер отправляет сообщение нескольким подписчикам через обменники.
  • Work Queue: Несколько потребителей обрабатывают сообщения из одной очереди, распределяя нагрузку.
  • Dead Letter Queue (DLQ): Очередь для сообщений, которые не удалось обработать.
  • Priority Queue: Сообщения обрабатываются в зависимости от их приоритета.

8. Основные компоненты Kafka? #

Apache Kafka — это распределённая система обработки потоков, состоящая из нескольких ключевых компонентов:

  • Producer (Производитель) — отправляет данные в Kafka.
  • Consumer (Потребитель) — получает данные из Kafka.
  • Event (Событие) - это основная единица данных, которая передается и хранится в системе. Состоит из ключа (опционально), значения, заголовков и метки времени.
  • Topics (Топики) — логическая сущность, разделяющая данные на каналы. Каждый топик хранит сообщения в порядке их поступления.
  • Partitions (Партиции) — подмножества топиков, которые разделяют данные для их распределенного хранения и параллельной обработки. Партиции дают возможность хранить данные на разных серверах.
  • Broker (Брокер) — сервер, ответственный за хранение данных в Kafka.
  • ZooKeeper — служит для управления и координации брокеров, поддерживает метаданные системы. Однако в новых версиях Kafka можно использовать KRaft (Kafka Raft), который заменяет ZooKeeper.
  • Consumer Group (Группа потребителей) — объединение консьюмеров, которые параллельно обрабатывают данные одного топика. Каждый консьюмер в группе обрабатывает разные партиции топика, что улучшает параллельность и производительность.

9. Kafka. Consumer Group #

Consumer Group — это механизм для группировки консьюмеров, работающих с одним топиком, для:

  • Распределения нагрузки: каждый консьюмер из группы читает данные только из одной или нескольких партиций, избегая дублирования. Например, если у нас три партиции и три консьюмера в группе, каждый из них будет работать с одной партицией.
  • Гибкости: можно масштабировать количество консьюмеров в группе для более быстрой обработки данных.

Консьюмеры внутри группы координируются так, что каждое сообщение обрабатывается ровно одним консьюмером в группе, что обеспечивает гарантию «один раз» для сообщений.


10. Из чего состоит Kafka кластер? #

Kafka-кластер — это распределенная система для передачи, хранения и обработки сообщений. Его основные компоненты:

КомпонентОписание
Брокеры (Brokers)Серверы, которые хранят данные и обрабатывают запросы производителей (producers) и потребителей (consumers). Каждый брокер имеет уникальный ID.
Топики (Topics)Логические категории для сообщений. Сообщения внутри топика распределены по разделам (partitions).
Разделы (Partitions)Логическое деление топика для масштабируемости. Сообщения в разделе упорядочены, между разделами порядок не гарантируется.
Реплики (Replicas)Копии разделов, хранящиеся на других брокерах для отказоустойчивости.
Контроллер (Controller)Один из брокеров, который управляет метаданными кластера и отвечает за перераспределение разделов в случае отказа.
Производители (Producers)Клиенты, которые публикуют сообщения в топики.
Потребители (Consumers)Клиенты, которые читают сообщения из топиков, обрабатывая данные в зависимости от задачи.
Consumer GroupГруппа потребителей, которые совместно обрабатывают сообщения из одного топика, разделяя нагрузку.
Zookeeper / KRaftСервис координации для управления метаданными кластера и состоянием брокеров (Zookeeper заменяется на KRaft в новых версиях Kafka).

11. Брокеры сообщений vs Rest #

ХарактеристикаБрокеры сообщений (Kafka, RabbitMQ)REST API
Модель коммуникацииАсинхронная (очереди, топики)Синхронная (запрос-ответ)
Скорость передачиВысокая, оптимизирована для работы с большими объемами данныхЗависят от производительности HTTP сервера
Надежность доставкиПоддерживает подтверждение (ACK), ретрансляцию сообщенийОбычно не предоставляет механизмов ретрансляции
МасштабируемостьВысокая, благодаря горизонтальному масштабированию брокеровТребует отдельных решений для масштабирования
Обработка данныхГибкая, подходит для потоковой обработки данныхПодходит для CRUD-операций
Сложность настройкиТребует установки и управления инфраструктуройЛегче развернуть, использует стандартный HTTP

12. Pull и push модели, в чём разница? #

Pull (вытягивание):

  • Описание:
    • Клиент запрашивает данные у сервера (или брокера).
    • Пример: Kafka, где потребители сами определяют, когда забирать данные.
  • Преимущества:
    • Клиент контролирует скорость обработки.
    • Легко обрабатывать данные на уровне клиента.
  • Недостатки:
    • Может возникнуть задержка, если клиент редко запрашивает данные.
    • Требует постоянного запроса данных.

Push (запихивание):

  • Описание:
    • Сервер отправляет данные клиенту по мере их поступления.
    • Пример: WebSocket, Firebase Cloud Messaging.
  • Преимущества:
    • Меньшая задержка: данные сразу отправляются клиенту.
    • Удобно для реального времени (например, уведомления).
  • Недостатки:
    • Клиент может быть перегружен большим потоком данных.
    • Контроль над потоком находится на сервере.

Kafka и модели:

  • Kafka использует Pull модель:
    • Потребители (Consumers) сами запрашивают данные у брокеров.
    • Это позволяет потребителям контролировать обработку данных, что особенно важно при высокой нагрузке.

13. Как гарантировать в Kafka идемпотентность, чтобы не было задвоений и потерь сообщений? #

Идемпотентность в Kafka означает, что одно и то же сообщение не будет записано несколько раз, даже если производитель (producer) повторно отправляет его из-за сбоев или переотправок.

Механизмы обеспечения идемпотентности:

  1. Включение идемпотентности у производителя:

    • Включите параметр enable.idempotence в конфигурации продюсера:

      Properties props = new Properties();
      props.put("enable.idempotence", "true");
      
    • Это позволяет Kafka отслеживать дубликаты сообщений с использованием уникального идентификатора производителя (Producer ID) и последовательного номера сообщения (Sequence Number).

  2. Ретрай на уровне продюсера:

    • Используйте конфигурацию retries для автоматической повторной отправки сообщений в случае временных ошибок:

      props.put("retries", Integer.MAX_VALUE);
      
  3. Репликация:

    • Убедитесь, что уровень подтверждений (acks) установлен в "all", чтобы гарантировать запись сообщения во все реплики:

      props.put("acks", "all");
      
  4. Idempotent Producer:

    • При включенной идемпотентности Kafka автоматически предотвращает дублирование сообщений на уровне брокера.

Идемпотентность и транзакции:

  • Используйте транзакционные сообщения, если нужно обеспечить атомарность для группы операций:

    props.put("transactional.id", "unique-transaction-id");
    producer.initTransactions();
    producer.beginTransaction();
    producer.send(...);
    producer.commitTransaction();
    

14. Как в Kafka гарантируется последовательность сообщений? #

Последовательность в рамках одного раздела (partition):

  1. Порядок внутри раздела:

    • Сообщения в одном разделе записываются последовательно. Kafka гарантирует, что потребитель получит их в том же порядке, в котором они были записаны.
  2. Производитель с одним соединением:

    • Чтобы сохранить порядок сообщений, производитель должен использовать одно соединение для записи в раздел.
  3. Подтверждения (acks):

    • Используйте acks=all для гарантии, что сообщение записано всеми репликами перед подтверждением.
  4. Идемпотентность:

    • Включение идемпотентности предотвращает нарушение порядка из-за повторной отправки сообщений.

Последовательность между разделами:

  • Kafka не гарантирует порядок сообщений между разными разделами топика, так как каждый раздел обрабатывается независимо.

15. Что сделать, чтобы сообщения попадали в одну партицию? #

Чтобы все сообщения с определенными характеристиками направлялись в одну партицию:

  1. Использование ключа (Key):

    • Укажите ключ сообщения (key), чтобы Kafka использовала его для хеширования и выбора раздела:

      producer.send(new ProducerRecord<>("topic", "key", "value"));
      
    • Сообщения с одинаковым ключом всегда будут направляться в один и тот же раздел.

  2. Настройка кастомного партишенера:

    • Реализуйте собственный Partitioner, чтобы задавать логику распределения:

      public class CustomPartitioner implements Partitioner {
          @Override
          public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
              // Кастомная логика выбора раздела
              return somePartitionIndex;
          }
      }
      
    • Зарегистрируйте кастомный партишенер в конфигурации:

      props.put("partitioner.class", "com.example.CustomPartitioner");
      
  3. Выбор раздела вручную:

    • Укажите номер раздела вручную при отправке сообщения:

      producer.send(new ProducerRecord<>("topic", 0, null, "value"));
      
    • Здесь 0 — номер раздела.


16. Как работает Round Robin? #

Round Robin — это стратегия распределения данных, при которой сообщения равномерно отправляются в разные разделы топика.

Механизм работы:

  1. Когда используется:

    • Производитель не задает ключ (key) при отправке сообщения.
    • Используется стандартный DefaultPartitioner.
  2. Логика:

    • Производитель отправляет первое сообщение в первый доступный раздел.
    • Следующее сообщение отправляется в следующий раздел, и так далее.
    • После достижения последнего раздела цикл повторяется с первого раздела.
  3. Пример:

    • Топик имеет 3 раздела:

      Partition 0: Msg1, Msg4
      Partition 1: Msg2, Msg5
      Partition 2: Msg3, Msg6
      

Особенности:

  • Round Robin обеспечивает равномерное распределение сообщений между всеми разделами.
  • Порядок сообщений не гарантируется между разделами.
  • Если разделы имеют разные лидеры, производитель сначала выбирает лидера для распределения нагрузки.

17. Acknowledgment в Kafka #

Acknowledgment (acks) — это механизм подтверждения записи сообщения в Kafka. Производитель получает подтверждение от брокера после записи сообщения.

Типы acks:

  1. acks = 0:

    • Производитель не ждет подтверждения.
    • Самая быстрая, но ненадежная стратегия (возможна потеря сообщений).
  2. acks = 1:

    • Производитель ждет подтверждения от лидера раздела.
    • Сообщение считается доставленным, если лидер записал его в лог.
    • Возможна потеря сообщений, если лидер сбоит до синхронизации реплик.
  3. acks = all (или -1):

    • Производитель ждет подтверждения от всех реплик, включая лидера.
    • Максимальная надежность: сообщения не теряются, если хотя бы одна реплика доступна.
    • Подходит для критичных данных.

Пример настройки acks:

Properties props = new Properties();
props.put("acks", "all");

18. Смещение (offset) в Kafka #

Offset — это уникальный идентификатор сообщения в рамках одного раздела. Он представляет собой смещение (номер) сообщения в логе.

Как работает offset:

  1. Уникальность:

    • Каждое сообщение в разделе имеет уникальный offset, начиная с 0.
  2. Использование:

    • Потребители используют offset, чтобы отслеживать, какие сообщения уже были обработаны.
    • Offset хранится в Kafka (по умолчанию в специальном топике __consumer_offsets) или в клиенте.
  3. Автоматическое и ручное управление:

    • Автоматическое управление (auto commit):

      • Kafka сама обновляет offset после чтения сообщения.
      • Настраивается параметром enable.auto.commit=true.
    • Ручное управление (manual commit):

      • Потребитель сам обновляет offset после успешной обработки сообщения.
      • Используется для большей надежности.
      consumer.commitSync(); // Обновление offset синхронно
      

Пример работы offset:

  • Топик с 2 разделами:

    Partition 0: Msg1 (offset=0), Msg2 (offset=1)
    Partition 1: Msg3 (offset=0), Msg4 (offset=1)
    
  • Потребитель может обрабатывать сообщения в любом порядке, используя offset как точку отсчета.


19. Повторное чтение данных из Kafka #

Kafka позволяет читать данные повторно, так как сообщения не удаляются сразу после потребления.

📌 Как прочитать данные повторно?

  1. Изменить offset у Consumer’а

    • Kafka хранит offset (позицию в топике) каждого Consumer’а.
    • Можно сбросить offset назад и прочитать сообщения повторно.

    📌 Пример:

    kafka-consumer-groups --bootstrap-server localhost:9092 --group my-group --reset-offsets --to-earliest --execute
    

    ✔ Устанавливает offset на самое начало (--to-earliest).

  2. Использовать enable.auto.commit=false

    • Если offset не фиксируется, Consumer будет читать данные заново при перезапуске.

    📌 Пример конфигурации:

    Properties props = new Properties();
    props.put("enable.auto.commit", "false"); // Offset не фиксируется автоматически
    
  3. Читать из __consumer_offsets

    • Kafka хранит offset’ы в системном топике __consumer_offsets.
    • Можно узнать текущий offset и переустановить его.

20. DLQ topic? #

Dead Letter Queue (DLQ) – это отдельный Kafka-топик, куда отправляются сообщения с ошибками.

📌 Зачем нужен DLQ?

  • Если Consumer не может обработать сообщение (например, ошибка десериализации).
  • Чтобы не блокировать обработку других сообщений.
  • Для повторной попытки обработки позже.

📌 Как настроить DLQ?

1️⃣ Вручную отправлять сообщения в DLQ

📌 Пример на Java:

try {
    processMessage(record);
} catch (Exception e) {
    ProducerRecord<String, String> dlqRecord =
        new ProducerRecord<>("dlq-topic", record.key(), record.value());
    kafkaProducer.send(dlqRecord);
}

✔ Если сообщение не обработалось, оно отправляется в DLQ-топик.

2️⃣ Использовать Kafka Connect DLQ

Если используется Kafka Connect, можно настроить DLQ автоматически:

errors.deadletterqueue.topic.name=dlq-topic
errors.deadletterqueue.context.headers.enable=true

✔ Сообщения с ошибками будут автоматически отправляться в DLQ.