Архитектор ГК Юзтех и интеграционной шины данных UseBus Добрый день, меня зовут Рустам Ахметов, я архитектор ГК Юзтех и интеграционной шины данных UseBus. В этой статье я расскажу о нашем опыте разработки продукта и выборе технического стэка. Хочу добавить, что я буду давать лишь поверхностный Helicopter view на продукты и их аналоги.  Из статьи вы узнаете: Что такое NiFi & Kafka и зачем она нужна; Какие есть популярные аналоги, и почему мы выбрали именно этот стэк. Я рассмотрю плюсы и минусы этих аналогов и объясню, почему это решение самое популярное на наш взгляд. В этой части статьи мы рассмотрим Kafka и её аналоги, а на следующей неделе подробнее остановимся на NiFi. В первую очередь хочу рассказать о Kafka. Можно сказать, что это Message Broker — некая очередь, через которую можно отправлять сообщения, на которые подписываются другие приложения и считывают их.  Теперь давайте перейдём к рассмотрению аналогов Kafka: что представлено на рынке, какие у них преимущества и недостатки.  Давайте подробнее рассмотрим несколько аналогов — RabbitMQ, ActiveMQ Artemis, IBM MQ и Pulsar. RabbitMQ — давний известный промышленный стандарт, он очень популярен, многие разработчики о нём знают. Можно сказать, что он популяризовал бинарный протокол AMPQ. Прекрасный протокол, который в своё время ускорил обмен сообщений с Rabbit и, в целом, был независим от языка программирования. На тот момент весь рынок возглавлял протокол JMS, который был завязан на язык программирования Java. Как только появился большой промышленный стандарт, который признали очень многие крупные компании, с протоколом, не привязанным к языку, все с удовольствием его поддержали.  RabbitMQ также отлично показал себя из-за того, что представляет собой достаточно комплексный готовый инструмент. В том плане, что вы можете его запустить, и сразу из коробки будут админка, метрики, его сразу можно использовать для обмена сообщениями и через админку смотреть, какие сообщения отправляются, какая нагрузка. Через метрики в продакшене можно сразу увидеть, что пошло не так и не обязательно что-то донастраивать. Вы можете его запустить и из коробки сразу пойдёт функционал. Поэтому RabbitMQ и стал в своё время очень популярен. Потому что даёт из коробки минимальный необходимый функционал для продакшена.  Наверное, он бы единолично завоевал весь рынок, если бы не одно критическое «но». Когда он был популярен, все пытались его масштабировать, кластеризовать каким-то образом, чтобы добиться отказоустойчивости — упала одна нода, но вторая продолжает работать. Но, к сожалению (я знаю по своему опыту и по опыту своих соратников), эти кластеры периодически зависали, разваливались, очереди внутри них зависали. В связи с этим RabbitMQ для серьёзных промышленных решений на десятки тысяч пользователей не снискал популярности. Потому что его нельзя было сделать в полной мере отказоустойчивым достаточно долгое время. И поэтому, чаще всего, его любят применять для не очень больших проектов, либо не очень требовательных с точки зрения отказоустойчивости (потому что ты его быстро запустил и он работает). За это его и полюбили.  Следующий продукт — это Apache ActiveMQ Artemis, который в своё время тоже гремел, был очень популярен и, насколько мне известно, его до сих пор любят использовать в связке с Apache Camel, потому что они отлично взаимно интегрируются.  Каков был его огромный плюс? В тот момент был очень популярен JMS протокол (даже не протокол, а API, можно сказать). Его особенность в том, что он очень мощный. Например, у него есть JMS-селекторы, которые позволяют из читаемой вами очереди отфильтровывать сообщения. Вы можете фильтровать их по идентификатору, по параметрам хедеров или ещё по каким-то вторичным признакам, и выбирать только те, которые вас интересуют. Сейчас я рассказываю только о какой-то части возможностей JMS, на самом деле это API чрезвычайно мощное и из-за этого в своё время полюбилась очень многим разработчикам.  Основной его минус связан с тем, что в полной мере использовать инструмент можно только на языке Java. И это сильно ограничивает его круг возможностей. Artemis также поддерживает AMQP, но тогда сразу теряются все возможности JMS, а они достаточно обширны. Собственно, из плюсов JMS вытекают и минусы. Из-за того, что достаточно приличную сложную логику фильтрации ActiveMQ готов брать на себя, он, как правило, гораздо медленнее, чем любое другое решение. Просто потому, что если вы на стороне своего сервера выполняете какую-то логику, то чем больше этой логики выполняете, тем медленнее работаете. Поэтому, с точки зрения потребителей это, конечно, более удобно, но с точки зрения сервера вы работаете гораздо медленнее. И это достаточно серьезный минус.  Но при этом он давным-давно умел легко масштабироваться. ActiveMQ далеко не молодое решение и уже много лет назад было способно к масштабированию и отказоустойчивости легко из коробки.  Стоит упомянуть IBM MQ, потому что в своё время это было крайне популярное решение. Десятилетиями большинство крупнейших организаций работало на IBM MQ. В том числе, за счёт коммерческой поддержки от самого IBM, за счёт возможности просить о каких-то услугах от самого вендора в лице IBM. То есть, поддержка была достаточно мощной и крутой. К сожалению, это решение очень старое: оно на API JMS. Мощное, но работает не очень быстро.  По своему опыту могу смело сказать: да, это решение умеет масштабироваться, собираться в кластеры. Но я сам лично видел, как кластеры разваливаются, периодически происходили split brain-ы, даже при достаточно стабильной сети (что не красит столь серьезный платный продукт). И, как я уже упоминал, он немолодой и поэтому сейчас от него все отказываются в пользу других, более молодых решений, которые активно развиваются. Я упоминаю это решение только потому, чтобы все понимали, что это такое. Наверняка вы ещё не раз с ним столкнётесь в какой-то крупной организации, которая не успела от него отказаться. Следующий аналог — Apache Pulsar. Довольно интересное, относительно молодое (если не ошибаюсь, оно стало на слуху где-то в 2018 году) решение, достаточно хорошо масштабируется и очень-очень быстро развивается. Поэтому мы ожидаем от этого решения очень многого в будущем и надеемся, что когда-нибудь оно выстрелит.  Я очень надеялся, что Pulsar заменит Kafka и будет гораздо удобнее. Но у Pulsar есть одна особенность: в данный момент он работает на одну ноду медленнее, чем Kafka. Так уж получилось, что по производительности Kafka до сих пор никто обогнать не может.  Одна заметка с точки зрения Message Broker, которые используют системное хранилище. Это важно, потому что тот же самый RabbitMQ умеет работать и в In-Memory. И, как правило, In-Memory работает быстрее, но если вы хотите гарантировать доставку, у вас серьёзная система, то работать в In-Memory, как минимум, опасно, потому что вы можете потерять сообщения, если приложение упадёт, а сообщение будет только в памяти приложения.  Pulsar так и не смог обогнать Kafka, и, похоже, может стать скорее заменой Apache ActiveMQ в будущем, потому что имеет скорость гораздо большую чем у ActiveMQ Artemis и богатый встроенный функционал из коробки. Основной минус Pulsar в данный момент видится в малом удобстве в виде UI для настройки и в целом высокую сложность настройки, порой более высокую даже чем у и без того непростой Kafka, в этом вопрос Pulsar подчистую проигрывает крайне удобному Artemis. Надеюсь, что мы вернёмся к этому решению через 2-3 года и посмотрим, станет ли оно промышленным стандартом. Вернемся к Kafkа. Итак, давайте разберёмся, почему мы, разрабатывая новый цифровой продукт, остановились на ней. Потому что это RPS на одну ноду. Я написал «одна из лучших RPS» с упоминанием про In-Memory Message Broker, но среди персистентных Message Broker, насколько мне известно, Kafka так и не была превзойдена. По производительности её пытаются догнать, но это всё ещё проблематично. У неё есть одна интересная особенность: с точки зрения кэша процессора и жёсткого диска она очень эффективно сохраняет сообщения на жёсткий диск, упорядоченно их записывает и читает, поэтому её тяжело обогнать. Максимум, что можно сделать на данном этапе, это попытаться догнать её по производительности. Фактически изначально её написали, ставя во главу угла эту эффективность записи на жёсткий диск — это была основная киллер-фича Kafka. И угнаться за ней всё ещё очень тяжело.  Также из плюсов я указал отказоустойчивость. Объясню почему: Kafka из коробки позволяет сходу реплицироваться и масштабироваться. Почему я выделяю это, как разные возможности? Потому что с точки зрения репликации это дублирование одних и тех же данных на разных нодах, а под масштабированием я понимаю и некое партицирование сообщений. Если у вас, например, один топик, вы можете партиции топика разделить на разные ноды и читать с разных нод. За счёт этого общая производительность на один топик увеличится.  В чём ещё особенность Kafka? Она работает по принципу распределённого лога. Как я уже упоминал, она эффективно записывает на жёсткий диск. Но ещё интересно то, что сообщения, вычитываемые из Kafka, не теряются после вычитывания. Например, у Rabbit, если вы прочитали сообщение, оно пропадает из queue, из очереди. И в этом проблема. Если вы хотите, чтобы на один и тот же поток сообщений подписалось пять разных приложений, там есть сущности exchange и queue. Поток входных сообщений складывается в exchange, а оттуда распределяется по разным queue. И приходится создавать и дублировать эти queue, из каждой очереди каждое приложение читает сообщения отдельно.  У этого решения есть как свои плюсы, так и минусы. В нашем случае, с точки зрения топика, просто один раз записали и можно повторить эту запись ещё раз, перечитать повторно эти же сообщения из этого же топика. Разные группы потребителей могут читать из одного же топика, совершенно не мешая друг другу, потому что фактически сообщения не удаляются после вычитывания. Но из этого вытекает проблема — сообщения не удаляются по вычитыванию и необходимо что-то думать, чтобы у вас не возникало переполнения памяти. И тут есть особенность, схожая с обычным логом. Если вы когда-то настраивали логи, то знаете, что вам приходится их ротировать. Проходит время, либо превышается какой-то размер на жёстком диске, и приходится выстраивать правила ротации, то есть очищения логов. И здесь то же самое. Да, Kafka можно настроить, чтобы она совсем не удаляла сообщения, но тогда рано или поздно вы упрётесь в то, что-либо память начнёт переполняться, либо придётся донастраивать какую-то сложную логику самостоятельного удаления сообщений. В принципе Kafka не на это рассчитана. Это будет не очень хорошо с точки зрения последовательной записи на жёсткий диск, с точки зрения эффективности работы самой Kafka, если вы сами будете влезать в особенности чистки сообщений.  Из плюсов — персистентность сообщений. Это чрезвычайно важно. Если у вас система, которая гарантирует доставку сообщений, вы не можете себе позволить ни в коем случае потерять сообщение по той или иной причине. Легко масштабируется, мы уже это обсуждали. Но я видел сообщения в интернете, как люди легко масштабировали кластер до двух миллионов записей чтений в секунду. Поэтому я это выразил, как RPS, что не совсем корректно, но два миллиона чтений и записей в секунду это достаточно хорошая производительность. И, судя по отзывам, люди не упираются в какой-то предел масштабирования, у них не было проблем и, по их собственным ощущениям, был ещё достаточно серьёзный задел на увеличение производительности. Это, конечно, огромный плюс Kafka, потому что она достаточно хорошо из коробки была заточена на производительность и масштабные отказоустойчивые решения.  Также из плюсов у Kafka есть такая интересная особенность: из-за того, что она стала очень популярной, у неё огромное количество разных плагинов, расширений, самописных админок, метрик и тому подобных интересных решений, которые могут расширять функциональность Kafka. Её высокая производительность достигается именно за счёт простоты. С точки зрения сложной бизнес-логики а-ля JMS, там её целенаправленно вычищали и говорили: «Если вам нужна сложная бизнес-логика, всё это должно быть на стороне потребителя или продюсера, но не в самой Kafka, чтобы ни в коем случае не снижать её производительность».  С одной стороны, это плюс, а с другой – минус, потому что в системе из коробки нет ничего с точки зрения удобства расширений бизнес-логики. И поэтому те же самые плагины имеют место быть, однако минусом выступает снижение производительности, потому что увеличивается нагрузка на саму Kafka. Вы можете докрутить эту особенность, когда чётко будете понимать, зачем вам это нужно. Многие из моих коллег, когда они только начинали разбираться в Kafka, рассказывали, что возникало ощущение, будто бы получили «разобранный двигатель». Потому что из коробки нет метрик, админки, надо что-то искать, выбирать, докручивать, думать. В принципе, как я уже писал ранее, минимализм — это хорошо, но иногда хочется какой-то пром стандарт. Например, я люблю язык программирования Java и у нас, в основном, используется Spring Framework, это признанный пром стандарт. Да, возможны и другие решения, но это решение достаточно хорошо себя зарекомендовало. Того же самого очень хочется от Kafka: ты её установил, ищешь самые популярные решения и с ходу их находишь.   Параметр высокой гибкости — это как плюс, так и минус. Да, Kafka можно настроить под себя и можно очень сильно докручивать, менять, конфигурировать.  Хочу привести следующий пример для наглядности, у Kafka есть разные режимы доставки сообщений — At least once guarantee, At most once guarantee и Exactly once guarantee. То есть, в зависимости от настройки, Kafka будет гарантировать, что она доставит сообщение хотя бы один раз, максимум один раз и точно один раз, не больше и не меньше. В зависимости от конфигурирования того или иного режима доставки сообщений, может возникать проблема снижения производительности. Почему это проблема? Потому что те же самые партиции топика могут реплицироваться между разными нодами и, соответственно, им нужно согласовываться между собой, если им придётся гарантировать какую-то доставку. Если мы выберем самое худшее — «Exactly once», то её производительность с точки зрения масштабирования достаточно сильно урезается. Поэтому необходимо чётко понимать, с точки зрения бизнес-логики какая доставка для вас приемлема и с какими минусами вы можете столкнуться. На самом деле таких примеров великое множество. Несмотря на то, что Kafka выполняет простейший минимальный функционал с точки зрения масштабирования, у неё достаточно большое количество вариаций конфигурирования. Это хорошо, и это же плохо, потому что Kafka очень глубокий инструмент и его нужно долго изучать, чтобы понимать, как правильно его докрутить.  Мы уже упоминали, что Kafka мало что может из коробки в плане маршрутизации сообщений. Простота в угоду быстродействию — это заложено в самом концепте Kafka и было сделано целенаправленно. Хорошо это или плохо? Каждый случай индивидуален и мы всегда должны это понимать во время выбора конкретного инструмента. Если мы хотим всё-таки докручивать серьёзную логику фильтрации, наверное, нужно посмотреть в сторону какого-то JMS-решения а-ля ArtemisMQ.  Итак, мы рассмотрели Kafka, её преимущества и недостатки, а также существующие на рынке аналоги. При разработке собственного продукта обращайте внимание на плюсы и минусы инструментов и выбирайте тот, что закроет ваши потребности. В следующей статье мы подробнее разберём NiFi: что это такое, как мы используем этот инструмент и какие аналоги существуют на рынке. Спасибо, что уделили время прочтению статьи. Оставляйте обратную связь в комментариях и расскажите о своих кейсах использования данных инструментов: что вам понравилось, а что нет? Что будете использовать?