Как гарантировать порядок сообщений от одного пользователя в одной партиции Kafka?
Что проверяют
Понимаете ли вы, что Kafka гарантирует порядок только внутри партиции, и знаете ли, где он ломается у продюсера и консьюмера. Следом почти всегда спрашивают, что будет при увеличении числа партиций.
Как отвечать
Порядок складывается из трёх частей:
- ключ: идентификатор пользователя кладётся в
KeyуMessage<string, string>. Партиционер считает хэш ключа, и все сообщения пользователя попадают в одну партицию. С числом пользователей число партиций никак не связано; - идемпотентность:
EnableIdempotence = trueвProducerConfig. Без неё повторная отправка после сетевой ошибки может записать второе сообщение раньше первого. В librdkafka, на которой построен Confluent.Kafka, она по умолчанию выключена, в отличие от свежих версий Java-клиента; - партиционер: в .NET-клиенте по умолчанию
consistent_randomна CRC32, а Java-клиент использует murmur2. Если в топик пишут сервисы на разных языках, выставьтеPartitioner = Partitioner.Murmur2Random, иначе один ключ разъедется по разным партициям; - консьюмер: партицию в группе читает ровно один консьюмер, но порядок легко потерять в коде, запустив пачку через
Task.WhenAll. Параллелить можно, только группируя по ключу. Оффсет коммитят после обработки, а обработчик делают идемпотентным: после ребалансировки сообщения приходят повторно.
Где спотыкаются
Предлагают завести партицию на каждого пользователя. Партиции стоят ресурсов брокера, и сотни тысяч пользователей так не разложить. Правильная мысль обратная: один горячий пользователь нагружает одну партицию, и это надо учитывать.
Забывают, что добавление партиций меняет отображение ключа на партицию. Новые сообщения пользователя уйдут в другую партицию, пока старые ещё не дочитаны, и порядок нарушится.
Отправляют упавшее сообщение в retry-топик и идут дальше. Следующие события того же пользователя обгонят отложенное, поэтому для упорядоченных потоков ретраи делают блокирующими по ключу.