Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 2

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

Recommended resources

Здравствуйте!

Введение

В прошлом уроке мы познакомились с основными компонентами Kafka: брокерами, топиками, партициями и офсетами. Мы научились создавать топики и обмениваться сообщениями с помощью консольных утилит. Вы могли заметить, что когда мы отправляли несколько сообщений, они, скорее всего, распределялись по разным партициям.

Сегодня мы углубимся в механизм, который управляет этим распределением.

Цель этого урока: Объяснить, как ключ сообщения (message key) влияет на распределение сообщений по партициям.

Понимание этого механизма является фундаментальным для построения надежных и предсказуемых систем на базе Kafka. Это позволит вам гарантировать, что связанные данные обрабатываются последовательно, что критически важно во многих backend-сценариях, таких как обработка транзакций пользователя или обновление состояния заказа.


1. Что такое ключ сообщения?

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

  • Значение (Value): Это сами данные, которые вы хотите передать. Например, JSON-объект с информацией о заказе.
  • Ключ (Key): Это опциональные метаданные, которые используются для одной конкретной цели — партиционирования. Ключ, как и значение, может быть строкой, числом или любым другим набором байт.

Ключ не является обязательным. Вы можете отправлять сообщения с ключом null. Как мы увидим, наличие или отсутствие ключа кардинально меняет поведение продюсера при отправке сообщения в топик с несколькими партициями.

Давайте посмотрим короткое видео, которое наглядно объясняет разницу в поведении.

Apache Kafka 101: Partitioning (2023)

Это видео от Confluent, компании-создателя Kafka, четко и лаконично объясняет, как Kafka распределяет сообщения с ключом и без него.

Посмотрите фрагменты, описывающие распределение сообщений без ключа (с 01:50 по 02:14) и с ключом (с 02:14 по 03:43). Обратите внимание на упоминание гарантии порядка для сообщений с одинаковым ключом.


2. Два сценария распределения сообщений

Как вы узнали из видео, существует два основных сценария распределения сообщений по партициям.

Сценарий 1: Сообщение без ключа (Key = null)

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

  • Поведение: Исторически использовался простой Round-Robin (циклический) алгоритм. Сообщение 1 отправлялось в партицию 0, сообщение 2 — в партицию 1, сообщение 3 — в партицию 2, сообщение 4 — снова в партицию 0, и так далее.
  • Современное поведение (Sticky Partitioner): Начиная с Kafka 2.4.0, для повышения производительности используется "липкий" партиционировщик. Продюсер отправляет несколько сообщений (батч) в одну партицию, а затем "переключается" на другую для следующего батча. Это снижает задержку и нагрузку на сеть. В конечном счете, сообщения все равно распределяются равномерно, но не строго поочередно.
  • Плюсы: Отличное распределение нагрузки. Все партиции и, следовательно, брокеры, на которых они находятся, используются равномерно.
  • Минусы: Нет никаких гарантий относительно порядка связанных сообщений. Два сообщения, относящиеся к одному и тому же заказу, могут попасть в разные партиции и быть обработаны в непредсказуемом порядке.

Сценарий 2: Сообщение с ключом

Когда вы указываете ключ, вступает в действие детерминированный механизм.

  • Поведение: Kafka гарантирует, что все сообщения с одинаковым ключом всегда будут попадать в одну и ту же партицию.
  • Плюсы: Это обеспечивает строгую гарантию порядка (ordering guarantee) для сообщений с одним и тем же ключом. Если вы отправляете события OrderCreated, OrderPaid, OrderShipped с одним и тем же orderId в качестве ключа, вы можете быть уверены, что они будут записаны в одну партицию и, следовательно, обработаны консьюмером в том же порядке.
  • Минусы: Если у вас есть "горячие" ключи (ключи, которые встречаются гораздо чаще других), это может привести к неравномерному распределению данных. Одна партиция может стать значительно больше и активнее других, создавая "узкое место".

Для закрепления этих концепций, пожалуйста, прочитайте следующий материал.

Is a Key Required as Part of Sending Messages to Kafka?

Статья на Baeldung, популярном ресурсе среди Java-разработчиков, кратко и ясно излагает значимость ключа в сообщении Kafka.

Прочитайте раздел 2, «Significance of a Key in a Kafka Message». Он суммирует все, что мы обсудили до сих пор.


3. Как работает партиционирование по ключу?

Механизм, который решает, в какую партицию отправить сообщение, называется партиционировщик (Partitioner). Логика его работы для сообщений с ключом довольно проста и должна быть вам знакома из опыта работы со структурами данных.

  1. Продюсер берет ключ сообщения.
  2. Вычисляет хэш от ключа. По умолчанию используется некриптографическая хэш-функция Murmur2.
  3. Берет остаток от деления (modulo) хэш-значения на количество партиций в топике.
  4. Полученное число и есть индекс целевой партиции.

Формула выглядит так: partition = hash(key) % num_partitions

Капча: Эта схема иллюстрирует, как ключи ('A', 'B', 'C', 'D') проходят через хэш-функцию Murmur2, и результат операции modulo с числом партиций (3) определяет целевую партицию для каждого сообщения. Например, для ключа 'B' хэш равен 15, и `15 % 3 = 0`, поэтому сообщение отправляется в Партицию 0.

Эта детерминированная природа хэширования гарантирует, что один и тот же ключ всегда будет давать один и тот же результат и, следовательно, попадать в одну и ту же партицию.

Капча: Еще одна визуализация процесса, где 'Partition Key' (например, ID департамента) обрабатывается хэш-функцией для выбора одной из доступных партиций.

Ваш опыт работы с HashMap в Java здесь является прямой аналогией. HashMap использует hashCode() объекта для определения "корзины" (bucket), в которой будет храниться значение. Партиционировщик Kafka делает то же самое, но для определения партиции.

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

Apache Kafka Partition Key: A Comprehensive Guide

Это руководство от Confluent «Apache Kafka Partition Key: A Comprehensive Guide» детально рассматривает механику и лучшие практики использования ключей.

Прочитайте следующие разделы: What is an Apache Kafka® Partition Key? и Understanding Partition Keys: для общего обзора. Hashing and Partition Assignment: для понимания механики хэширования. Importance of Partition Key in Message Ordering: чтобы закрепить важность ключей для сохранения порядка. Best Practices for Using Kafka Partition Keys: обратите внимание на советы по выбору ключа для равномерного распределения. Partition Key vs. Round-Robin Partitioning: для прямого сравнения двух подходов.

4. Выбор правильного ключа

Как вы узнали из статьи, выбор ключа — это важное архитектурное решение.

  • Для сохранения порядка: Используйте идентификатор сущности, для которой важна последовательность событий. Например:
    • customerId для всех действий клиента.
    • orderId для всех этапов обработки заказа.
    • deviceId для всех показаний с IoT-устройства.
  • Для равномерного распределения: Если порядок не важен, но вы хотите сгруппировать некоторые данные, выбирайте ключ с высокой кардинальностью (большим количеством уникальных значений). Использование ключа с низкой кардинальностью (например, region со значениями "EU", "US", "Asia") в топике с 10 партициями приведет к тому, что только 3 партиции будут использоваться, а остальные будут простаивать.


Заключение

Сегодня мы разобрали один из самых важных механизмов в Kafka — партиционирование по ключу.

Ключевые выводы:

  • Ключ сообщения — это опциональные данные, определяющие, в какую партицию будет отправлено сообщение.
  • Если ключ null, сообщения распределяются равномерно по всем партициям для максимальной утилизации ресурсов (Round-Robin или Sticky partitioning). Порядок связанных сообщений не гарантируется.
  • Если ключ задан, все сообщения с одинаковым ключом гарантированно попадают в одну и ту же партицию. Это обеспечивает строгую последовательность обработки для этих сообщений.
  • Партиция определяется по формуле: hash(key) % number_of_partitions.
  • Выбор ключа — это компромисс между гарантией порядка и равномерным распределением нагрузки.

Что дальше?

Теория важна, но нет ничего лучше практики. В следующем уроке мы перейдем к коду. Мы напишем Java-продюсер с использованием kafka-clients, который будет отправлять сообщения как с ключом, так и без него. Затем мы проверим, в какие партиции попали наши сообщения, чтобы на практике убедиться в работе механизмов, которые мы сегодня изучили.