Недавно я как обычно играл в Factorio после рабочего дня — и вдруг меня поразила удивительная мысль. Как много здесь аналогий с Apache Kafka!


Если у вас мало свободного времени, не скачивайте Factorio

Для тех, кто в последние годы путешествовал за пределами цивилизации, на всякий случай поясню: Factorio — это стратегия реального времени с открытым миром, где вы строите и оптимизируете цепочки поставок, чтобы запустить спутник и восстановить связь с родной планетой, а Kafka — это распределённая платформа потоковой передачи событий, которая обрабатывает асинхронные коммуникации надёжным способом.

Если человек вообще никогда не работал с потоковой платформой, то ему станет всё понятно на примерах из игры. Что ж, давайте начнём с нуля, изучим основные концепции Kafka — и немного повеселимся.

Зачем нужен асинхронный обмен сообщениями?


Допустим, у нас три микросервиса. Один для добычи железной руды, второй для переплавки руды в листовой металлопрокат, а третий — для производства шестерён из этого проката. Мы можем связать все сервисы в цепочку с помощью синхронных HTTP-вызовов. Как только бур добывает новую руду, он отправляет вызов POST на плавильную печь, которая, в свою очередь, отправляет POST на фабрику.


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

Всё было отлично, пока на фабрике не отключили электричество. Тогда HTTP-вызовы от печи не сработали, что в свою очередь привело к сбою вызовов от бура. Конечно, чтобы избежать каскадных сбоев и потери сообщений, можно реализовать прерыватели цепи ('circuit breaker' на рисунке) и повторные попытки…



… но в какой-то момент придётся прекратить попытки, иначе у нас закончится память.


Отключение электроэнергии на заводе

Если бы только разделить эти микросервисы… И здесь на помощь приходит Kafka. С её помощью можно надёжно хранить потоки записей с гарантированной защитой от сбоев. В терминологии Kafka эти потоки называются «темы» (топики).


Разделение микросервисов с помощью асинхронной связи

При использовании асинхронных тем во время пиковых нагрузок и при перебоях все записи буферизируются. Очевидно, у буферов ограниченная ёмкость. Поэтому поговорим о масштабируемости.

Мы можем увеличить ёмкость хранилища и пропускную способность, добавив серверы в кластер. Другой способ — нарастить диски, CPU и ширину канала. Какой из этих вариантов даст наилучшее соотношение цены и качества — зависит от конкретного случая использования. Но увеличение размера серверов — в отличие от увеличения их количества — подчиняется закону убывающей отдачи. Пропускная способность Kafka линейно растёт с каждым добавленным узлом, так что обычно это оптимальный вариант.


Вертикальное масштабирование — серверы большего размера с экспоненциальным ростом стоимости


Горизонтальное масштабирование — распределение нагрузки на большее количество серверов

Чтобы разделить тему на несколько серверов, нужно разбить её на более мелкие подпотоки. Эти подпотоки в Kafka называются разделами. Когда служба производит новую запись, она выбирает раздел, куда её поместить.


Вагон производит записи. Разделитель (partitioner) переносит сообщения в нужный раздел. И тема с четырьмя разделами

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

return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;

Так сообщения с одинаковым ключом всегда оказываются в одном разделе.

Обратите внимание, что сообщения гарантированно упорядочены только в контексте производителя и раздела. Записи от нескольких производителей или от одного производителя в нескольких разделах могут чередоваться.



Теперь, когда мы знаем, как сообщения помещаются в темы, давайте посмотрим, как они потребляются. Когда вы начинаете прослушивать тему, по умолчанию вам направляются записи со всех разделов. Однако часто бывает так, что несколько инстансов микросервиса работают одновременно для достижения более высокой пропускной способности или доступности. Если все они начнут прослушивать тему, то каждая запись будет обрабатываться каждым инстансом, а это обычно не оптимальный вариант.


Все инстансы микросервиса потребляют все сообщения

Равномерно разделить разделы между несколькими потребителями помогают группы потребителей. Когда инстанс микросервиса присоединяется к группе потребителей, Kafka переназначает ему часть разделов. Аналогично, когда инстанс выходит из строя или по какой-то другой причине покидает группу, его разделы назначаются другим инстансам. Kafka следит, чтобы разделы всегда равномерно распределялись между потребителями в каждой группе.


Одна группа потребителей с тремя членами

Если в разделах разное количество записей, могут возникнуть проблемы. Один инстанс может не успевать, потому что ему назначен раздел с большим количеством записей, в то время как другие инстансы простаивают. Следует убедиться, что во всех разделах примерно одинаково записей.


В слишком загруженном разделе накапливается очередь сообщений

Каждый потребитель отслеживает, какие записи он обработал. Поскольку записи обрабатываются по порядку, достаточно простого смещения (offset). Время от времени (по умолчанию каждые 5 секунд) потребитель фиксирует своё смещение в Kafka (коммит).

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

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



Аналогия перестаёт работать при дублировании данных. В Kafka мы можем обработать одну запись несколько раз. Несколько групп потребителей могут потреблять одни и те же записи. Для надёжности темы можно хранить с коэффициентом репликации три. У тем может быть период хранения, по истечении которого записи удаляются. Всё это возможно, потому что записи легко дублировать, в отличие от физических объектов в Factorio.

На этом можно закончить. Благодаря любимой игре мы рассмотрели практически все основные концепции Kafka и получили общее представление, как она работает. Подведём краткий итог.

Что мы узнали


Kafka — это распределённая платформа потоковой передачи событий, которая хранит записи долговременным образом путём репликации на нескольких серверах. Темы состоят из разделов, которые хранят записи по порядку. Разделители решают, какие записи относятся к каким разделам. Группы потребителей необязательны и помогают распределить разделы между потребителями для масштабируемости. Смещения фиксируются как контрольные точки на случай сбоя потребителей.

Вот вкратце и всё, как работает Kafka.

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


  1. akryukov
    26.11.2021 13:21
    +1

    Не понял, в чем асинхронность решения "Разделение микросервисов с помощью асинхронной связи". У вас там все соединено строго последовательно, вы только за счет конвейеров немного увеличили буфер для хранения.

    Так, как вы сделали на картинке "Все инстансы микросервиса потребляют все сообщения" лучше не делать. У вас получилось на входе три конвейера и три манипулятора, а пропускная способность этой системы в целом - один манипулятор.


    1. fedorro
      26.11.2021 16:16
      +1

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

      а пропускная способность этой системы в целом — один манипулятор.
      — улучшенный манипулятор может по три предмета за раз переносить, и если скорость изготовления одной шестерни в три раза меньше скорости одного манипулятора на полной загрузке — то норм) Ну и буфер же… ????


      1. akryukov
        27.11.2021 23:09

         каждый завод получает уникальную плитку, на самом деле.

        Проблема аналогий Kafka с факторио в том, что плитки - не уникальны.

        Какой первичный ключ у плитки?

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

        А еще в Kafka одно и то же сообщение можно прочитать несколько раз разными потребителями. Детальки на заводе в этом смысле "одноразовые".


  1. sedyh
    26.11.2021 13:40
    +4

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

    Кажется, в вашей схеме должен быть belt balancer.


  1. arvitaly
    26.11.2021 16:41

    Аналогия перестаёт работать при дублировании данных. В Kafka мы можем обработать одну запись несколько раз. Несколько групп потребителей могут потреблять одни и те же записи. Для надёжности темы можно хранить с коэффициентом репликации три. У тем может быть период хранения, по истечении которого записи удаляются. Всё это возможно, потому что записи легко дублировать, в отличие от физических объектов в Factorio.

    Если проводить аналогии с kafka, то в качестве данных логично было бы считать физические объекты в factorio, после строительства (аналог получения сообщения от продьюсера) они занимают фиксированное место на плоскости (логи), могут быть разрушены кусаками (retention) и к ним могут подключить ограниченное количество других приспособлений (соответствие количества партиций - консьюмерам). Можно пофантазировать на тему того, что KTable и собратья - это аналог хранилищ внутри печей, к примеру :-)


  1. kohus
    26.11.2021 18:29
    +1

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


  1. korsetlr473
    26.11.2021 19:49

    1) в 2021 появились в кафке какие способы чтобы прям из сайта получать поток из кафки ? без звена в виде прослойки бэкенд сервиса

    2) больше вопрос к @arvitaly он походу разбирается , при оконной агрегации появилась фича чтобы только финальный результат агрегации показывался\отправлялся в топик?


    1. kuznetsovkd
      27.11.2021 00:51
      +3

      способы чтобы прям из сайта получать поток из кафки

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


    1. arvitaly
      27.11.2021 06:58

      1) Если вопрос в том, появился ли в kafka http/websocket/whatever-web интерфейс, то нет, и никогда не появится, это явный overhead желаний по отношению к брокеру сообщений. Посмотрите в сторону rethinkdb.

      2) Вы можете с помощью подключенного консьюмера делать что угодно, аггрегировать и становясь продьюсером посылать результат в новый топик.


  1. nikitsenka
    27.11.2021 07:37

    ДА, аналогия прекрасная. Cам играл и также думал. Вот только есть один моментик которого в кафке очень не хватает. Управление backpreassure(обратное давление). Eсли консюмеры не успeвают обрабатывать сообщения, то продюсеры об этом никогда не узнают и будут пихать в кафку пока не кончится память а дальше просто посыпятся ошибки. В отличии от факторио где к примеру печи отключаются если на ленте нет места.


    1. Tsimur_S
      27.11.2021 12:31

      В отличии от факторио где к примеру печи отключаются если на ленте нет места.

      Сначала наполняется сама печь а потом останавливается производство. В переносе на реальный проект с кафкой это бы выглядело так — продюсер наполняет свой внутренний буфер а потом просто стопает процесс в проде(перестает реагировать на события приводящие к генерации сообщений). Не думаю что это бы выглядело адекватно.


      1. nikitsenka
        27.11.2021 13:39

        Я не про внутренний буфер а про механизм оповещения продюсеров. В переводе на кафку это выглядело бы так - продюсер пытается записать в кафку и получает сигнал что топик переполнен. Далее продюсер останавливает производство месседжей пока кафка не прислала сигнал что можно продолжать. На сколько знаю сейчас такого механизма в кафка нет и продюсер в лудшем случае будет делать ретрай с увеличивающимся по экспоненте таймаутом, а в худшем будет просто продолжать слать меседжы и засорять трафик


    1. xeeaax
      27.11.2021 20:57
      +2

      Потому что кафка это не про пересылку объекта получателю, а про публикацию и возможность прочитать. Принципиально другая идеология. Со своими плюсами и минусами.

      Backpreassure может быть самодельное, второй топик, куда потребитель пишет статус.

      Зато тут может быть "из коробки" подход, когда новый запрос на ту же тему просто перетирает старый неактуальный. Через компактизацию топиков по ключу.


      1. nikitsenka
        27.11.2021 23:39
        -3

        Идеология в кафке? Не понимаю о чем это вы? В документации про это не написано. Кафка это инструмент у которого есть свои возможности и ограничения а идеология это в политике а не тех продуктах