company_banner

RabbitMQ против Kafka: два разных подхода к обмену сообщениями

    В прошлых двух статьях мы рассказывали об IIoT — индустриальном интернете вещей — строили архитектуру, чтобы принимать данные от сенсоров, паяли сами сенсоры. Краеугольным камнем архитектур IIoT да и вообще любых архитектур работающих с BigData является потоковая обработка данных. В ее основе лежит концепция передачи сообщений и очередей. Стандартом работы с рассылкой сообщений сейчас стала Apache Kafka. Однако, для того, чтобы разобраться в ее преимуществах (и понять ее недостатки) было бы хорошо разобраться в основах работы систем очередей в целом, механизмах их работы, шаблонах использования и основной функциональности.



    Мы нашли отличную серию статей, которая сравнивает функциональность Apache Kafka и другого (незаслуженно игнорируемого) гиганта среди систем очередей — RabbitMQ. Эту серию статей мы перевели, снабдили своими комментариями и дополнили. Хотя серия и написана в декабре 2017 года, мир систем обмена сообщениями (и особенно Apache Kafka) меняется так быстро, что уже к лету 2018-го года некоторые вещи изменились.


    Источник

    RabbitMQ vs Kafka


    Рассылка сообщений (messaging) — центральная часть множества архитектур, и двумя столпами в этой сфере являются RabbitMQ и Apache Kafka. К настоящему моменту Apache Kafka стала практически индустриальным стандартом в обработке данных и аналитике, поэтому в этой серии мы детально рассмотрим RabbitMQ и Kafka в контексте их использования в инфраструктурах реального времени.


    Apache Kafka сейчас на подъеме, а о RabbitMQ как будто бы стали забывать. Весь хайп сконцентрировался на Kafka, и это происходит по понятным причинам, но RabbitMQ по-прежнему является отличным выбором для обмена сообщениями. Одна из причин, по которой Kafka переключила внимание на себя — общая одержимость масштабируемостью, и, очевидно, Kafka более масштабируема, нежели RabbitMQ, но большинство из нас не имеет дел с масштабами, при которых у RabbitMQ появляются проблемы. Большинство из нас не Google и не Facebook. Большинство из нас имеет дело с ежедневными объемами сообщений от сотен тысяч до сотен миллионов, а не с объемами от миллиардов до триллионов (но кстати, существуют кейсы, когда люди масштабировали RabbitMQ до миллиардов ежедневных сообщений).


    Таким образом, в нашей серии статей мы не будем говорить о случаях когда требуется экстремальная масштабируемость (и это прерогатива Kafka), а сосредоточимся на уникальных преимуществах, которые предлагает каждая из рассматриваемых систем. Что интересно, у каждой системы свои преимущества, но при этом они довольно сильно отличаются друг от друга. Я, конечно, довольно много писал про RabbitMQ, но уверяю, что никакого особого предпочтения ей не отдаю. Мне нравится хорошо сделанные вещи, а RabbitMQ и Kafka обе вполне зрелые, надежные и, да, масштабируемые messaging-системы.


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


    RabbitMQ против Kafka: два разных подхода к обмену сообщениями


    В этой части мы рассмотрим, что такое RabbitMQ и Apache Kafka, и их подход к обмену сообщениями. Обе системы подходят к архитектуре обмена сообщениями с разных сторон, у каждой из которых имеются сильные и слабые стороны. В этой главе мы не придем к каким-либо важным выводам, вместо этого, предлагаем воспринимать эту статью как пособие по технологиям для начинающих, для того, чтобы мы могли погрузиться глубже в следующих статьях серии.


    RabbitMQ


    RabbitMQ — это распределенная система управления очередью сообщений. Распределенная, поскольку обычно работает как кластер узлов, где очереди распределяются по узлам и, опционально, реплицируются в целях устойчивости к ошибкам и высокой доступности. Штатно, она реализует AMQP 0.9.1 и предлагает другие протоколы, такие как STOMP, MQTT и HTTP через дополнительные модули.


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


    Exchange'и и очереди


    Супер-упрощенный обзор:


    • Паблишеры (publishers) отправляют сообщения на exchange’и
    • Exchange’и отправляют сообщения в очереди и в другие exchange’и
    • RabbitMQ отправляет подтверждения паблишерам при получении сообщения
    • Получатели (consumers) поддерживают постоянные TCP-соединения с RabbitMQ и объявляют, какую очередь(-и) они получают
    • RabbitMQ проталкивает (push) сообщения получателям
    • Получатели отправляют подтверждения успеха/ошибки
    • После успешного получения, сообщения удаляются из очередей

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


    Давайте посмотрим пример работы с одним паблишером, exchange’м, очередью и получателем:



    Рис. 1. Один паблишер и один получатель


    Что делать, если у вас несколько паблишеров одного и того же
    сообщения? Что делать, если у нас есть несколько получателей, каждый из которых хочет получать все сообщения?



    Рис. 2. Несколько паблишеров, несколько независимых получателей


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



    Рис. 3. Несколько паблишеров, одна очередь с несколькими конкурирующими получателями


    На рисунке 3 мы видим трех получателей, которые используют одну очередь. Это конкурирующие получатели, то есть они конкурируют за получение сообщений из очереди. Таким образом, можно ожидать, что в среднем каждый получатель будет получать одну треть сообщений из очереди. Мы используем конкурирующих получателей для масштабирования нашей системы обработки сообщений, а с помощью RabbitMQ сделать это очень просто: добавьте или удалите получателей по запросу. Независимо от того, сколько у вас конкурирующих получателей, RabbitMQ обеспечит доставку сообщений только одному получателю.


    Мы можем скомбинировать рис. 2 и 3, чтобы получить несколько наборов, конкурирующих получателей, где каждый набор получает каждое сообщение.



    Рис. 4. Несколько паблишеров, несколько очередей с конкурирующими получателями


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


    Гарантии


    RabbitMQ дает гарантии «одноразовой доставки» и «хотя бы одной доставки», но не «ровно одной доставки».


    Примечание переводчика: до версии Kafka 0.11 доставка сообщений exactly-once delivery была недоступна, в настоящее время подобная функциональность присутствует и в Kafka.


    Сообщения доставляются в порядке их прибытия в очередь (в конце концов, это и есть определение очереди). Это не гарантирует, что завершение обработки сообщений совпадает с тем же самым порядком, когда у вас есть конкурирующие получатели. Это не ошибка RabbitMQ, а фундаментальная реальность параллельной обработки упорядоченного набора сообщений. Эту проблему можно решить, используя Consistent Hashing Exchange, как вы увидите в следующей главе по шаблонам и топологиям.


    Проталкивание (push) и предварительная выборка получателей


    RabbitMQ проталкивает (push) сообщения получателям (существует также API для выгрузки (pull) сообщений из RabbitMQ, но в настоящий момент эта функциональность устарела). Это может переполнить получателей, если сообщения прибудут в очередь быстрее, чем получатели могут их обработать. Чтобы этого избежать, каждый получатель может настроить предел предварительной выборки (также известный как предел QoS). По сути, предел QoS это ограничение на количество скопившихся неподтвержденных получателем сообщений. Это действует как предохранитель, когда получатель начинает отставать.


    Зачем принято решение о том, что сообщения в очереди проталкиваются (push), а не выгружаются (pull)? Во-первых, потому, что так меньше время задержки. Во-вторых, в идеале, когда у нас есть конкурирующие получатели из одной очереди, мы хотим равномерно распределить нагрузку между ними. Если каждый получатель запрашивает/выгружает сообщения, то в зависимости от того, сколько они запрашивают, распределение работы может стать довольно неравномерным. Чем более неравномерно распределение сообщений, тем больше задержка и дальнейшая потеря порядка сообщений во время обработки. Эти факторы ориентируют архитектуру RabbitMQ на механизм проталкивания “одно-сообщение-за-раз”. Это одно из ограничений масштабирования RabbitMQ. Ограничение смягчается тем, что подтверждения можно группировать.


    Маршрутизация


    Exchange’и — это, в основном, маршрутизаторы сообщений для очередей и/или других exchange’ей. Чтобы сообщение перемещалось из exchange’а в очередь или на другой exchange, необходима привязка. Разные exchange’и требуют разных привязок. Существует четыре типа exchange’ей и связанные с ними привязки:


    • Fanout (разветвляющий). Направляет во все очереди и обменники, имеющие привязку к exchange’у Стандартная подмодель Pub.
    • Direct (прямой). Маршрутизирует сообщения на основе ключа маршрутизации, который несет с собой сообщение, задается паблишером. Ключ маршрутизации — короткая строка. Прямые обменники отправляют сообщения в очереди/exchange’и, у которых есть ключ сопряжения, который точно соответствует ключу маршрутизации.
    • Topic (тематический). Маршрутизирует сообщения на основе ключа маршрутизации, но позволяет использовать неполное соответствие (wildcard).
    • Header (заголовочный). RabbitMQ позволяет добавлять к сообщениям заголовки получателей. Заголовочные exchange’и передают сообщения в соответствии с этими значениями заголовка. Каждая привязка включает в себя точное соответствие значений заголовка. К привязке можно добавить несколько значений с ЛЮБЫМИ или ВСЕМИ значениями, необходимыми для соответствия.
    • Consistent Hashing (консистентное хэширование). Это обменник, который хэширует либо ключ маршрутизации, либо заголовок сообщения, и отправляет только в одну очередь. Это полезно, когда вам нужно соблюсти гарантии порядка обработки и при этом иметь возможность масштабировать получателей.


    Рис. 5. Пример topic exchange’а


    Мы еще рассмотрим маршрутизацию более подробно, но выше приведен пример topic exchange’а. В этом примере паблишеры публикуют журналы ошибок с использованием формата ключа маршрутизации LEVEL(Уровень ошибки).AppName.


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


    Очередь 2 получит любой уровень ведения журнала приложения ECommerce.WebUI. Она использует wildcard *, таким образом захватывая уровень одного именования топика (ERROR.Ecommerce.WebUI, NOTICE.ECommerce.WebUI итп).


    В очереди 3 будут отображаться все сообщения уровня ERROR из любого приложения. Она использует wildcard # для охвата всех приложений (ERROR.ECommerce.WebUi, ERROR.SomeApp.SomeSublevel итп).


    Благодаря четырем способам маршрутизации сообщений и с возможностью exchange-ам передавать сообщения на другие exchange’и RabbitMQ позволяет использовать мощный и гибкий набор шаблонов обменов сообщениями. Дальше мы поговорим об exchange’ах с недоставленными сообщениями (dead letter exchange), об exchange’ах и очередях без данных (ephemeral exchanges and queues), и RabbitMQ развернется в полную мощь.


    Exchange’и с недоставленными сообщениями


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


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


    • Очередь превышает заданное количество сообщений.
    • Очередь превышает заданное количество байт.
    • Время передачи сообщения (TTL) истекло. Паблишер может установить время жизни сообщения, и сама очередь тоже может иметь заданное TTL для сообщения. В таком случае будет использоваться более короткий TTL из двух.

    Мы создаем очередь, которая имеет привязку к exchange’ам с недоставленными сообщениями, и эти сообщения сохраняются там до тех пор, пока не будут предпринято какое-либо действие.


    Как и многие функции RabbitMQ, exchange’и с недоставленными сообщениями дают возможность использовать шаблоны, которые непредусмотренны изначально. Мы можем использовать TTL сообщений и exchange’и с недоставленными сообщениями для реализации отложенных очередей и повторных очередей.


    Обменники и очереди без данных


    Exchange’и и очереди могут быть созданы динамически, при этом можно задать критерии для их автоматического удаления. Это позволяет использовать такие шаблоны, как очереди ответов для RPC на основе сообщений.


    Дополнительные модули


    Первый подключаемый модуль, который вы наверняка захотите установить — это плагин управления, который предоставляет HTTP-сервер с веб-интерфейсом и REST API. Он очень прост в установке и у него простой в использовании интерфейс. Развертывание сценариев через REST API тоже очень простое.


    Кроме того:


    • Consistent Hashing Exchange, Sharding Exchange и многое другое
    • протоколы, такие как STOMP и MQTT
    • веб-хуки
    • дополнительные типы обменников
    • интеграция SMTP

    Существует еще множество вещей, которые можно рассказать про RabbitMQ, но это хороший пример, который позволяет описать что RabbitMQ может делать. Теперь мы рассмотрим Kafka, который использует совершенно иной подход к обмену сообщениями и, при этом, также имеет свой набор отличительных, интересных возможностей.


    Apache Kafka


    Kafka — это распределенный реплицированный журнал фиксации изменений (commit log). У Kafka’и нет концепции очередей, что сначала может показаться странным, учитывая, что его используют в качестве системы обмена сообщениями. Очереди долгое время были синонимом систем обмена сообщениями. Давайте для начала разберемся, что значит «распределенный, реплицированный журнал фиксации изменений»:


    • Распределенный, поскольку Kafka развертывается как кластер узлов, как для устойчивости к ошибкам, так и для масштабирования
    • Реплицированный, поскольку сообщения обычно реплицируются на нескольких узлах (серверах).
    • Журнал фиксации изменений, потому что сообщения хранятся в сегментированных, append-only журналах, которые называются топиками. Эта концепция журналирования является основным уникальным преимуществом Kafka’и.

    Понимание журнала (и топика) и партиций являются ключом к пониманию Kafka’и. Итак, чем партиционированный журнал отличается от набора очередей? Давайте представим, как это выглядит.



    Рис. 6 Один продюсер, один сегмент, один получатель


    Вместо того, чтобы помещать сообщения в очередь FIFO и отслеживать статус этого сообщения в очереди, как это делает RabbitMQ, Kafka просто добавляет его в журнал, и на этом все.


    Сообщение остается, вне зависимости от того, будет ли оно получено один или несколько раз. Удаляется оно в соответствии с политикой удерживания данных (retention policy, также называемый window time period). Каким же образом информация забирается из топика?


    Каждый получатель отслеживает, где она находится в журнале: имеется указатель на последнее полученное сообщение и этот указатель называется адресом смещения. Получатели поддерживают этот адрес через клиентские библиотеки, и в зависимости от версии Kafka адрес сохраняется либо в ZooKeeper, либо в самой Kafka’е.


    Отличительная особенность модели журналирования в том, что она мгновенно устраняет множество сложностей, касающихся состояния доставки сообщений и, что более важно для получателей, позволяет им перематывать назад, возвращаться и получать сообщения по предыдущему относительному адресу. Например, представьте, что вы разворачиваете сервис, который выставляет счета, учитывающие заказы, размещаемые клиентами. У службы случилась ошибка, и она неправильно рассчитывает все счета за 24 часа. С RabbitMQ в лучшем случае вам нужно будет как-то переопубликовать эти заказы только на сервисе счетов. Но с Kafka вы просто перемещаете относительный адрес для этого получателя на 24 часа назад.


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



    Рис. 7. Один продюсер, одна партиция, два независимых получателя


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


    Теперь предположим, что сервис выставления счетов необходимо разделить на три части, потому что она не может поспеть за скоростью сообщения. С RabbitMQ мы просто разворачиваем еще два приложения-сервиса выставления счетов, которые получают из очереди обслуживания счетов. Но Kafka не поддерживает конкурирующих получателей в одной партиции, блок параллельности Kafka — это сама партиция. Поэтому, если нам нужны три получателя счетов, нам нужно как минимум три партиции. Итак, теперь у нас есть:



    Рис. 8. Три партиции и две группы по три получателей


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


    Партиции и группы получателей


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


    Сообщения могут быть перенаправлены на сегменты по циклическому алгоритму или через функцию хэширования: hash (message key) % количество партиций. Использование функции хэширования имеет некоторые преимущества, поскольку мы можем создавать ключи сообщений, чтобы сообщения от одного объекта, например, информация о бронированиях, которая должна быть обработана последовательно, всегда переходили в один сегмент. Это позволяет использовать многие шаблоны работы с очередями и гарантии очередности сообщений.


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


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


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


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


    Эта возможность существует и в RabbitMQ — с помощью Consistent Hashing exchange, который одинаково распределяет сообщения по очередям. Однако особенность Kafka’и заключается в том, что Kafka организует эту упорядоченную обработку таким образом, что только один получатель в каждой группе может получить сообщения из одной партиции, и упрощает работу, поскольку, чтобы обеспечить соблюдение этого правила, для вас всю работу выполняет узел-координатор. В RabbitMQ все равно могут быть конкурирующие получатели, получающие одну очередь от партиции, и вам придется убедиться, что этого не происходит.


    Здесь также есть подводные камни: в тот момент, когда вы меняете количество париций, сообщения с номером Id 1000 теперь переходят в другую партицию, поэтому сообщения с номером Id 1000 существуют в двух партициях. В зависимости от того, как вы обрабатываете сообщения, это может вызвать проблему. Теперь существуют сценарии, когда сообщения обрабатываются не по порядку.


    Проталкивание (push) против выгрузки (pull)


    RabbitMQ использует модель проталкивания (push) и, таким образом, перегрузку сообщений получателями с помощью настроенного получателем предела предварительной выборки. Это отлично подходит для обмена сообщениями с низким значением задержки и хорошо работает для архитектуры RabbitMQ на основе очереди. С другой стороны, Kafka использует модель вытягивания (pull), где получатели запрашивают партии сообщений с заданного относительного смещения. Чтобы избежать бесконечных пустых циклов, когда никаких сообщений не существует за пределами текущего относительного адреса, Kafka допускает long-polling.


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


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


    Публикация и подписка


    Kafka поддерживает базовый шаблон продюсер/подписчик» с некоторыми дополнительными шаблонами, связанными с тем, что это журнал и он имеет партиции. Продюсеры добавляют сообщения в конец разделов журнала, и получателей с их относительными адресами можно разместить в любом месте партиции.



    Рис. 9. Получатели с различными относительными адресами


    Этот стиль диаграммы не так легко интерпретировать, когда есть несколько париций и групп получателей, поэтому для остальной части диаграмм для Kafka я буду использовать следующий стиль:


    Рис. 10. Один продюсер, три партиции и одна группа из трех получателей


    Нет необходимости иметь такое же количество получателей в нашей группе получателей, поскольку есть партиции:



    Рис. 11. Некоторые получатели считывают из нескольких партиций


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


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



    Рис. 12. Один бездействующий получатель


    После добавления и удаления получателей группа получателей может стать несбалансированной. Перебалансировка перераспределяет получателей по партициям как можно более равномерно.



    Перебалансировка автоматически запускается после того как:


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

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


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


    Сжатие журнала


    Стандартная политика хранения данных — это политика на основе времени и пространства. Например, хранение до последней недели сообщений или до 50 ГБ. Но существует другой тип политики хранения данных – сжатие журнала. Когда журнал сжимается, результатом является то, что сохраняется только последнее сообщение для каждого ключа сообщения, остальные удаляются.


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


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


    Подробнее об упорядочении сообщений


    Мы рассмотрели, что масштабирование и поддержание порядка сообщений одновременно возможно как с RabbitMQ, так и с Kafka, но с Kafka это намного проще. С RabbitMQ мы должны использовать консистентное хэширование и вручную внедрять логику группы получателей, используя распределенный обобщенный сервис, такой как ZooKeeper или Consul.


    Но у RabbitMQ есть одна интересная возможность, которой нет у Kafka. Это не является особенностью самой RabbitMQ, а любой системы обмена сообщениями, основанной на очереди на основе подписки. Возможность такова: системы обмена сообщениями на основе очереди позволяют подписчикам упорядочивать произвольные группы событий.


    Давайте разберемся немного поподробнее. Различные приложения не могут разделять очередь между собой, потому что тогда они будут конкурировать за получение сообщений. Им нужна их собственная очередь. Это дает приложениям свободу конфигурировать свою очередь любым образом, каким они посчитают нужным. Они могут направить несколько типов событий из нескольких топиков в свою очередь. Это позволяет приложениям поддерживать упорядочение связанных событий. Те события, которые потребуется объединить, могут быть настроены по-разному для каждого приложения.


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


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


    Выводы


    RabbitMQ предлагает широкий спектр шаблонов обмена сообщениями благодаря множеству функциональных возможностей, которые у него есть. Благодаря своей полнофункциональной маршрутизации он может избавить получателей от необходимости извлекать, десериализовывать и проверять каждое сообщение, когда ему требуется только подмножество. С ним легко работать, масштабирование вверх и вниз осуществляется путем простого добавления и удаления получателей. Его архитектура дополнительных модулей позволяет ему поддерживать другие протоколы и добавлять новые функции, такие как консистентное хэширование обменов, что является важным дополнением.


    Распределенный журнал Kafka с относительными адресами получателей делает возможным путешествие во времени. Его возможность маршрутизировать сообщения с одним и тем же ключом одному и тому же получателю, в свою очередь делает возможной чрезвычайно параллелизованную упорядоченную обработку. Сжатие журнала Kafka и сохранение данных позволяют создавать новые шаблоны, которые RabbitMQ просто не может выполнить. Наконец, хотя Kafka и может масштабироваться больше, чем RabbitMQ, большинство из нас имеет дело с таким объемом сообщений, который оба подхода могут обработать без проблем.


    В следующей части мы более подробно рассмотрим шаблоны обмена сообщениями и топологии с RabbitMQ.


    Завели, кстати, тематический чат в телеграме про IoT и все, что с этим связано. Присоединяйтесь: t.me/justiothings
    ITSumma
    121,00
    Собираем безумных людей и вместе спасаем интернет
    Поделиться публикацией

    Комментарии 39

      +2
      Незаслуженно обделили Apache ActiveMQ Artemis, он во многом перспективнее RabbitMQ при функциональной совместимости решения.
        +1
        спасибо, обязательно посмотрим!
        это серия из 5-и статей которая сравнивает именно кролика и кафку, но мы посмотрим как можно вписать
          0
          С удовольствием почитаю. Спасибо за обзор!
          0
          Если у Артемиды AMQP 1.0 «под капотом», то о какой «функционально совместимости» (с «кроликом», как я понимаю) может идти речь?!

          Что касается самой статьи, то практически всё, что в ней сказано про RabbitMQ (за исключением, возможно, «масштабируемости»), собственно, к самому «кролику» имеет весьма опосредованное отношение. Оно всё про AMQP «нулевой» серии.

          А поскольку, авторы, судя по всему, в AMQP разбираются «ниочинь» (это я про оригинал), то — лично у меня — возникает «смешенное чувство» от «рассуждений» про «использует push», про «конкурирующих получателей» и т.п.
            0
            В статье было сказано про интернет вещей и протоколы STOMP, MQTT. В этом контексте и прокомментировал. В плане версии AMQP все верно, но в java приложениях его используют через JMS клиентов и в этом случае решения функционально совместимы.
              0
              Я понял. Только нужно помнить, что если «под капотом» не AMQP «нулевой» серии, то у вас нет ни exchange'ей, ни к ним «привязанных» очередей и т.п. Поэтому вся «функциональная совместимость» — в таком случае — сводится к «мы умеем слать сообщения».

              … но в java приложениях его используют через JMS клиентов и в этом случае решения функционально совместимы.

              Вообще-то нет. Т.к. на «движок» AMQP 0.9, 0.10 «ложится» практически любой messaging — в том числе, и AMQP 1.0. А вот наоборот — уже нет. Т.е. про все «плюшки» маршрутизации «на брокере» уже можно будет забыть.

              Либо у нас сильно разное понимание «функциональной совместимости».
                0
                Спасибо что просветили! Не являюсь знатоком протоколов AMQP 0.9, 0.10 vs AMQP 1.0. Благодаря вашим замечаниям, по результатам чтения документации сложилось впечатление что главное отличие в том что AMQP 1.0 концентрируется на описании протокола и не регламентирует архитектуру брокера, exchanges и bindings. Но при этом в AMQP 1.0 возможны взаимодействия точка-точка без брокера, что может лучше подходить для mesh network в некоторых сценариях обмена IoT устройств.

                За свою практику приходилось работать с Tibco EMS, Amazon SQS, Kafka, Solace(только через JMS API), Universal Messaging(Nirvana) и когда нужен сложный роутинг по контенту сообщений, то он выносился на уровень приложения либо на Apache Camel route. Бриджами баловались только в Tibco, в остальных системах без них спокойно жили. Так что без маршрутизации на брокере как-то обходились. Часть систем были банковским ПО, часть проектов распределенные «числодробилки».

                На практике от сервиса очередей сообщений требуется поддержка транзакций, гарантированная доставка с какой-либо из семантик и требуемая системой пропускная способность для персистентных сообщений, еще хорошо если есть «из коробки» поддержка dead letter queue. Сложные же вещи лучше переложить на полноценный message routing engine.

                С точки зрения что не получится заменить RabbitMQ на Artemis, при использований всех фич RabbitMQ вы правы. Но если же рассматривать в контексте RabbitMQ vs Kafka или Artemis vs Kafka, то Artemis может лучше подойти для IoT приложений чем RabbitMQ. Так что видимо у нас действительно разное понимание функциональности, совместимости.
          +3
          Спасибо за статью, много хорошей информации.

          Одно дополнение.
          Когда вы имеете дело с ИИОТ то важным требованием является возможно апгрейда микросервисов с 0 остановкой (Blue/Green/Canary).
          Использование потоковых инструментов типа кафки или кинесиса позволяет деплоить с существенно более простой архитектурой, поскольку поток всегда можно пере-проиграть снова в случае откатов.

          Для многих «edge» сервисов мы используем стримы именно по этому поводу.
            0

            Извините за возможный офтопик. Мне нужна очередь для выполнения определенных вычислений (каждая задача относится к конкретному пользователю). Но таким образом, чтобы задачи, относящиеся к одному пользователю, не передавались на обработку одновременно. Есть ли возможность сделать это на какой-то "стандартной" очереди? Или придется делать свое решение? Кафка, судя по описанию, умеет группировать события по признаку, но мне не нравится, что может получиться ситуация, когда один воркер ничего не делает, а второй перегружен задачами.

              +3
              Это нормально ложится на AMQP «нулевой» серии. Основная идея в том, что вам надо разделить «потребителей», которые производят вычисления (не важно, какого пользователя), и «потребителей», которые осуществляют диспетчеризацию задач на вычисление (для конкретного пользователя). Конечно это все сильно проще «нарисовать», но…

              Логика работы «вычислителя» примерно следующая:
              1. Из очереди T получаем сообщение m, без ack;
              2. На основе данных из сообщения проводим вычисления;
              3. Результат вычислений r отсылаем c m.replyTo;
              4. Делаем ack на T и переходим к 1.

              Логика работы «диспетчера» примерно такая:
              1. Из очереди U получаем сообщение t, без ack;
              2. Формируем сообщение m = t, m.replyTo = UR;
              3. Посылаем m в T;
              4. Получаем из UR сообщение r;
              5. Делаем ack на U и переходим к 1.

              Очередь T – одна на все. Её «слушают» все «вычислители». Кол-во «вычислителей» делать больше чем кол-во пользователей смысла не имеет, т.е. к в T всегда будет не больше сообщений чем пользователей (от каждого, только одно сообщение).

              Очереди U/UR — у каждого пользователя свои. «Диспетчер» может быть один на все U/UR (условно, «сложный»), или у каждой U/UR он будет свой (условно, «простой»). Важно, что у конкретной U/UR только один конкретный «диспетчер».

              Входной поток задач можно «раскладывать» по U, например, ч/з topic.
              Для обмена U->T->UR достаточно и direct.
                +1
                кажется, вы слегка усложняете.

                в случае с кафкой можно просто взять один топик (с фиксированным количеством партишенов) и в качестве ключа для партицирования использовать user_id.
                каждый partition разгребать в один поток.
                все задания этого юзера гарантированно будут там и обработаются последовательно.
                думаю, что в 99% процентов случаев никаких проблем с перфомансом не будет.

                либо как альтернатива — брать абсолютно любой брокер, в нем одну очередь, разбирать в любое количество потоков, а перед обработкой записи пробовать брать лок в каком-то стороннем месте (mysql SELECT GET_LOCK в базе где хранится юзер или в zookeper/consul). Конкретный юзер сейчас залочен? Сделали этому эвенту postpone и попробовали чуть позже (вопрос реализации postpone зависит уже от брокера)
                  0

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


                  Второй вариант с постпоном для меня не работает, так как задача должна уходить на обработку сразу, как только освобождается воркер. Непонятно, как э то организовать в механике postpone и не клинически большим пинанием задачи в очередь и из очереди.

                  0
                  plantuml нарисовал бы по описанию
                    0

                    Спасибо, звучит разумно. Этакий буферизующий воркер/диспетчер получается (если один "сложный").

                      0
                      А можно поподробнее про нулевую и ненулевую версию? Я думал, что есть вот просто AMPQ. Каждая последующая версия чуть лучше и с большим количеством фич чем предыдущая. А сейчас оказывается, что все совсем иначе…
                        0
                        «Поподробнее» — это на отдельный пост дел :-)

                        Если «на пальцах», то AMQP 1.0 — он не «лучше/хуже»… он просто *другой*. В том смысле, что это не *обновление* AMQP 0.10 (который, по понятным причинам, практически нигде не поддерживается) — это из серии «забудьте про топор — вот вам лопата».

                        Понятно, что понравилось это далеко не всем… и состав AMQP Working Group сильно — скажем так — изменился очень быстро.

                          0
                          И что теперь использовать (если мы говорим про новый проект)? Я так понял есть депрекейтед-стандарт который используется де-факто, и новый, который никому не понравился и который не поддерживается.

                          Бтв отдельный пост думаю был бы очень кстати. Можно от вас его ожидать? Лично я с удовольствием прочитал бы.
                            0
                            И что теперь использовать (если мы говорим про новый проект)?

                            Честно говоря — я не знаю. С одной стороны, «нулевая» серия — мертва. Т.е. она не развивается *централизовано". Основная причина, понятно, юридическая. И то, что кто-то таки пытается что-то «допиливать» — бьет по одной из самых ценных вещей «нулевой» серии — по интероперабельности. Плюс (точнее минус), нет абсолютно никаких гарантий в плане того, что его внезапно не перестанут вообще поддерживать с какого-то момента. Хотя, в таком состоянии он уже больше пяти лет :-) Альтернатив-то нет. Подвижки к «реанимации» были… но, тот же OpenAMQ «умер» очень быстро… причины, насколько я понимаю, те же. Т.е. проблема в «области права».

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

                            Я так понял есть депрекейтед-стандарт который используется де-факто, и новый, который никому не понравился и который не поддерживается.

                            Немножко не так… AMQP *был* стандартом, т.к. его рабочая группа состояла из «инженерных титанов». Которые разрабатывали именно *стандарт*… в инженерном смысле. Сейчас это далеко не так.

                            Ну и 1.0 не то, что «никому не понравился»… он не понравился тем самым «титанам». Но на их место пришла пара-тройка «вендоров». Которым возможность «привязать к себе» еще и этим очень даже понравилась.

                            Бтв отдельный пост думаю был бы очень кстати. Можно от вас его ожидать? Лично я с удовольствием прочитал бы.

                            Это сильно вряд ли… в ближайшее время — по крайней мере. И я — честно говоря — слабо представляю, что вы хотите узнать из этого поста. Писать про «чем лопата отличается от топора» желания у меня не много :-(
                              +1
                              И я — честно говоря — слабо представляю, что вы хотите узнать из этого поста. Писать про «чем лопата отличается от топора» желания у меня не много :-(

                              Да про все. Что за титаны, что за инженерные группы, почему новый стандарт это не стандарт, что за юридические нюансы… Для меня всё это новая информация, которую я нигде не встречал. Причем крайне важная. Потому что я вообще теперь перестал понимать, что мне использовать если я хочу очередь. Раньше я был уверен, что есть два живых конкурента, которые развиваются и делают интересные штуки, а теперь оказывается все совсем иначе…
                                +1
                                Что за титаны …

                                Из известных это, например, Cisco и Novell. Из менее известных — TWIST, например.

                                … почему новый стандарт это не стандарт…

                                По факту. «Нулевая» серия описывала, емнип, General Puprose Middleware *Standard*, а AMQP 1.0 всего лишь Advanced Message Queueing *Protocol* :-)

                                Если чуть серьезней, то спецификации «нулевой» серии описывала MOM целиком — т.е. от wire-layer, и до семантики обработки. И, собственно, *протокол* amqp — это, хоть и существенная, но лишь часть этих спецификаций.

                                Реализация этих спецификаций *гарантировала* полную интероперабельность узлов такой MOM. Что, в свою очередь, означало независимость от какого-либо конкретного вендора. Т.е. все как «у взрослых».

                                А спецификация AMQP 1.0 — формально — вообще не описывает МОМ. Она описывает некий транспортный уровень «в вакууме». Т.е. использовать это можно только «прикрутив» к какой-либо *уже существующей* МОМ — что, собственно, мы и наблюдаем.

                                Интероперабельность «узлов» *возможна* только на транспортном уровне — т.к. никакого другого в спецификации просто нет. Собственно, там и на транспортном уровне, много чего нет. Помнится, я — в свое время — словил много адреналина пытаясь выяснить *что именно* теперь связывает Connection. Это точно не инженеры писали.

                                Ну и понятно, что в таком виде оно нужно «не только лишь всем». И если лет пять назад в AMQP Working Group оставались Huawei и INETCO, то сейчас *инженерных* компаний там не осталось *совсем*. Что — лично для меня — весьма показательно.

                                … что за юридические нюансы…

                                Права на спецификации «нулевой» серии принадлежат, емнип, OASIS. И она как-то не горит желанием выпускать их в public domain :-) Т.е. вы не можете «просто так» взять спецификацию AMQP 0.10 и на её основе сделать свой AMQP 1.0 «with blackjack and hookers» (с)
                    +2

                    Спасибо за статью!


                    1. Предположим, что у нас одна очередь и 10 получателей в RabbitMQ. Мы захотели ускорить обработку сообщений в 10 раз — вжух — увеличиваем количество получателей до 100, и сообщения обрабатываются быстрее. Насколько просто это сделать в Kafka, где у каждой партиции строго один получатель?
                    2. В RabbitMQ, когда у каждой очереди много получателей, выход из строя одного из получателей не является катастрофой. А что происходит, если единственный получатель у партиции в Kafka выходит из строя? Кто обрабатывает "зависшие" в партиции данные?
                      +2
                      Насколько это может быть просто – зависит от начальных параметров очереди.

                      Единица параллелизма в Kafka – это partition, и если очередь создается со слишком маленьким количеством partitions, отмасштабироваться будет затруднительно: один получатель может обрабатывать несколько partitions, а вот одна partition читаться несколькими получателями – нет.
                        +1
                        Единица параллелизма в Kafka – это partition, и если очередь создается со слишком маленьким количеством partitions, отмасштабироваться будет затруднительно

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

                          Сам вендор (Confluent) предлагает максимально внимательно отнестись к этому вопросу и выпустил хорошее руководство по подбору параметра: How to choose the number of topics/partitions in a Kafka cluster
                            +1
                            Какбы вы там ни прищуривались, а жизнь не ограничивается одними стресс-тестами.
                              0
                              Да. Но и без них тоже тяжело.
                          0
                          Единица параллелизма в Kafka – это partition

                          Стоит также отметить, что (наверное) все драйверы Кафки получают сообщения блоками (т.е. по N сообщений за раз) и некоторые из них предоставляют возможность параллельной обработки сообщений. Впрочем, это легко может быть реализовано и самостоятельно.
                            0

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

                            0
                            По второму вопросу, вроде как, в кафке все еще несколько проще. Новый/перезапущенный получатель вполне может знать место, на котором «сдох» предыдущий.
                              +1
                              Второй вопрос решается запуском нескольких консьюмеров с одинаковым group_id и эти консьюемеры будут как один логический консьюмер. Кафка будет сама балансить партиции между консьюмерами. Если два инстанса консьюмера с одинаковым group_id и две партиции, то каждый консьюмер будет обрабатывать свою партицию, если один консьюмер упадёт, то второй будет читать из двух партиций.
                              –1

                              Кажется у Вас опечатка в "Рис. 8. Три партиции и две группы по три получателей", там по логике должно быть "Рис. 8. Три партиции и три группы по два получателя"

                                +4
                                Главная боль рэббита — абсолютная неготовность к кластеризации. Причина — mnesa, которая готова устроить split brain по любому чиху. Например, если сделать ноде (erlang'овой) suspend, а потом resume (чтобы проэмулировать лаг), то мы получим split brain, который либо нерешаем, либо приводит к потери информации. И любые мысли «у нас нет таких лагов» натыкаются на вопрос: у вас нет, а у вашего компьютера? Interrupt storm, баг в acpi, thermal throttling, баги в ядре — и всё, процесс радостно считает, что он один живой, а все пиры сдохли. А пиры считают так же.

                                В standalone режиме он вполне хорош, если не считать постоянного wtf из-за изобилия эрланговых глупостей (двустрочный логгинг, переключение между beamp/beam.smp в зависимости от числа ядер и т.д.).
                                  0
                                  Они могут направить несколько типов событий из нескольких топиков в свою очередь.… Это просто невозможно с помощью системы обмена сообщениями на основе журнала, такой как Kafka, поскольку журналы являются общими ресурсами.

                                  В кафке консумер может читать одновременно из нескольких топиков, не вижу принципиальной разницы.
                                    0

                                    Я правильно понимаю, что обе эти системы предназначены для одностороннего обмена информацией? То есть источник сообщений может передать информацию потребителю, а потребитель не может вернуть источнику результат.

                                      +1
                                      Двусторонний обмен — это два односторонних.
                                        0
                                        в rabbitmq есть механизм rpc — можно в сообщении указать «положи мне ответ в такую-то очередь»
                                        0
                                        Спасибо за перевод!
                                        Ничего не сказано про масштабируемость кластера. Это одна из проблем Кафки. Ее невозможно масштабировать под нагрузкой, тк дополнительная репликация на новые брокеры в кластере вызовет повышенное потребление ресурсов и станет еще хуже.
                                        То есть кластер лучше держать несколько over provisioned.
                                        А что с RabbitMQ с масштабированием под нагрузкой?
                                          0

                                          Просто нужно расширяться заранее, а не когда жареный петух клюнет.


                                          А кролик вообще в контексте одной очереди не масштабируем — клиенты про кластер ничего не знают. Нужно партиционировать со стороны приложения вручную как то

                                          0

                                          Может кто знает для RabbitMQ есть какая-нибудь система мониторинга типа Kibana? Чтобы json логи онлайн можно было удобно фильтровать и разворачивать для просмотра?

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

                                          Самое читаемое