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

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

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

Apache Kafka

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

Основные характеристики

  • Акцент на потоковом контенте, работа с большими потоками данных.

  • Основные возможности: обеспечение сохранности сообщений и их многократная повторная обработка.

  • Хостинг на месте и поддержка сторонних модулей. 

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

Технические особенности развертывания

Apache предлагает SDK на нескольких языках.

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

Многие компании предлагают хостинг Kafka в качестве услуги (например, AWS, CloudKarafka и Aiven) или на их виртуальных машинах.

Ниже приведен пример кода на JavaScript для начала работы с событиями Apache Kafka.

const { Kafka } = require('kafkajs')
const kafka = new Kafka({
 clientId: 'my-app',
 brokers: ['localhost:9092']
})

// this produces a message
async function produce() {
 const producer = kafka.producer()
 await producer.connect()
 await producer.send({
   topic: 'TOPIC_NAME',
   messages: [
     { key: 'key1', value: 'hello world' },
   ],
 })
}

async function consume() {
 const consumer = kafka.consumer({ groupId: 'my-group' })
 await consumer.connect()
 await consumer.subscribe({ topic: 'TOPIC_NAME' })
 await consumer.run({
   eachMessage: async ({ topic, partition, message }) => {
     console.log({
       key: message.key.toString(),
       value: message.value.toString(),
       headers: message.headers,
     })
   },
 })
}

Преимущества и недостатки

Kafka ориентирован на высокую пропускную способность потока данных, это видно по его статистике производительности.

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

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

RabbitMQ

RabbitMQ — это еще один брокер сообщений с открытым кодом. Первоначальным разработчиком была компания Rabbit Technologies, но в результате ряда приобретений продукт перешел в собственность VMware.

Основные характеристики

  • Акцент на обмене сообщениями с возможностью поддержки больших потоков данных.

  • Основная особенность — расширенный функционал маршрутизации.

  • Хостинг на месте и поддержка сторонних модулей. 

RabbitMQ также использует модель «Публикации — подписки», отправляя объекты сообщений в двоичной форме в различные именованные очереди, которые могут динамически создаваться и уничтожаться.

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

Технические особенности развертывания

Для RabbitMQ существует несколько клиентских библиотек на множестве языков.

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

Следующий код на Node.js с пакетом AMQPLIB иллюстрирует простейший пример работы с RabbitMQ.

const amqp = require('amqplib/callback_api');
amqp.connect('amqp://localhost', function(error0, connection) {
 if (error0) {
   throw error0;
 }
 connection.createChannel(function(error1, channel) {
   if (error1) {
     throw error1;
   }
   const queue = 'hello-queue';
   const msg = 'Hello world!';

   channel.assertQueue(queue, {
     durable: false
   });

   // Sending message to queue
   channel.sendToQueue(queue, Buffer.from(msg));
   console.log("Sent message", msg);

   // Consuming messages
   channel.consume(queue, function(msg) {
     console.log("Received message", msg.content.toString());
   }, { noAck: true });
 });
});

Преимущества и недостатки

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

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

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

Amazon Web Services (AWS) SQS/SNS

SNS и SQS представляют собой примеры двух разных подходов к распределенному обмену сообщениями.

SNS в значительной степени ориентирован на доставку сообщений. С помощью модели «Публикации — подписки» он позволяет быстро передавать сообщения множеству клиентов, например мобильным устройствам, конечным точкам HTTPS или другим сервисам AWS.

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

Основные характеристики

  • Возможность передавать широковещательные сообщения и работать по модели «Публикации — подписки».

  • Быстрая настройка с помощью AWS.

  • Отсутствие хостинга за пределами AWS.

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

В SNS применяется режим push-уведомлений, который позволяет автоматизировать ответы. SQS больше ориентирован на механизм опроса с поддержкой некоторых дополнительных функций, управляемых событиями.

Технические особенности

AWS предлагает общий SDK с доступом к большинству сервисов AWS (включая SQS и SNS) на нескольких популярных языках.

Ниже приведен код, который демонстрирует работу с сервисами SNS и SQS при помощи SDK AWS.

// SNS - publish
const AWS = require('aws-sdk');
AWS.config.update({ region: 'REGION' });

const publishParams = {
 Message: 'MESSAGE_TEXT',
 TopicArn: 'TOPIC_ARN'
};

const publishTextPromise = new AWS.SNS({ apiVersion: '2010-03-31' }).publish(publishParams).promise();

publishTextPromise.then(
 function(data) {
   console.log(`Message ${publishParams.Message} sent to topic ${publishParams.TopicArn}`);
 }).catch(
 function(err) {
   console.error(err, err.stack);
 });

// SNS - Subscribe
const subscribeParams = {
 TopicArn : 'TOPIC_ARN'
}
const subscribePromise = new AWS.SNS({ apiVersion: '2010-03-31' }).listSubscriptionsByTopic(subscribeParams).promise();
subscribePromise.then(
 function(data) {
   console.log(data);
 }).catch(
 function(err) {
   console.error(err, err.stack);
 }
);

// SQS - send
const sqs = new AWS.SQS({ apiVersion: '2012-11-05' });
const queueURL = "SQS_QUEUE_URL";

const sendParams = {
 DelaySeconds: 10,
 MessageAttributes: {
   "Title": {
     DataType: "String",
     StringValue: "Some String"
   }
 },
 MessageBody: "Something!",
 QueueUrl: queueURL
};

sqs.sendMessage(sendParams, function(err, data) {
 if (err) {
   console.log("Error sending to SQS", err);
 } else {
   console.log("Success sending to SQS", data.MessageId);
 }
});

// SQS - receive
const receiveParams = {
 AttributeNames: [
   "SentTimestamp"
 ],
 MaxNumberOfMessages: 10,
 MessageAttributeNames: [
   "All"
 ],
 QueueUrl: queueURL,
 VisibilityTimeout: 20,
 WaitTimeSeconds: 0
};

sqs.receiveMessage(receiveParams, function(err, data) {
 if (err) {
   console.log("Receive Error", err);
 } else if (data.Messages) {
   console.log("Received messages:", JSON.stringify(data.Messages))
 }
});

Преимущества и недостатки

При совместном использовании AWS SQS и SNS могут стать основой масштабируемого надежного распределенного приложения. Благодаря интеграции со множеством других сервисов AWS (например, Lambda) эти два инструмента могут помочь с легкостью расширить коммуникационные возможности вашего приложения и предоставить обширный инструментарий для разрешения проблем взаимодействия между сервисами.

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

В отличие от Kafka и RabbitMQ, которые не ограничивают размер сообщений по умолчанию, AWS устанавливает некоторые ограничения для сообщений SQS и SNS и после достижения определенного размера преобразует сообщения в объекты S3.

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

https://www.aspecto.io/blog/how-to-send-large-sqs-sns-messages-with-node-js/
https://www.aspecto.io/blog/how-to-send-large-sqs-sns-messages-with-node-js/

Так как SQS и SNS построены в соответствии с принципом Cloud First, дополнительная сложность их использования вызвана привязкой к конкретному провайдеру. Другие брокеры сообщений избавлены от этого благодаря возможности локальной установки и сопровождения.

Выбор подходящего брокера сообщений

При выборе брокера вам следует принять во внимание два фактора:

Фактор 1: тип отправляемых сообщений

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

Фактор 2: характер вашей ежедневной деятельности и инфраструктура приложений

Тут свою роль могут сыграть второстепенные обстоятельства. Подумайте о своей ежедневной работе и своих системах и задайте себе следующие вопросы:

  • Вы создаете приложение только в AWS? Возможно, для взаимодействия между службами будет целесообразнее использовать SQS и SNS.

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

  • Вам требуются скорость доставки и минимальная задержка? Тогда Kinesis — то, что вам нужно (рассмотрим его в другой статье, так что следите за обновлениями). При этом для приложения, которое ориентировано на гарантированную доставку и избыточность, может потребоваться другая технология. 

На этом уровне ваш выбор зависит от требований инфраструктуры приложения и характера работы.

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

  • Если для вас важны сохранность сообщений и возможность многократной повторной обработки — ваш выбор должен пасть на Kafka.

  • Если вас больше волнует возможность поддерживать и внедрять сложный набор правил маршрутизации — вам лучше всего подойдет RabbitMQ.

  • Если у вас небольшой стартап и вы хотите быстрее приступить к работе с минимальными накладными расходами — для вас отличным вариантом станут AWS SQS и SNS, учитывая их быструю настройку и структуру расходов.

Сквозная видимость в процессе обмена сообщениями

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

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

По ссылке далее руководства можно загрузить инструмент OpenTelemetry Kafkajs Instrumentation для Node.js

https://www.aspecto.io/blog/how-to-achieve-end-to-end-microservices-visibility-in-asyn-messaging-with-opentelemetry/
https://www.aspecto.io/blog/how-to-achieve-end-to-end-microservices-visibility-in-asyn-messaging-with-opentelemetry/

Выводы

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

Сообщения и брокеры, которые их доставляют, будут критически важны в инфраструктуре, которая управляет вашим приложением.

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

Связанные репозитории GitHub

Библиотека производителей и потребителей SQS/SNS. Дает возможность передавать полезные нагрузки через S3.

OpenTelemetry Kafkajs Instrumentation для Node.js. Модуль представляет собой автоматический инструментарий для Kafka.js.

П.С. От переводчика

Выбор инструментов часто упирается не только в их возможности, но частенько зависит от возможностей команды по поддержке и сопровождению этих инструментов. Для снижения различных затрат, связанных с сопровождением, можно обратиться к Managed сервисам. Например, в Yandex.Cloud можно воспользоваться Yandex Managed Service for Apache Kafka​ и сразу сфокусироваться на создании приложений обработки потока данных, и не тратить время на поддержание инфраструктуры. А если хочется использовать привычные инструменты взаимодействия с Amazon SQS на помощь прийдет — Yandex Message Queue.

Для тех кто выбирает serverless решение в Yandex.Cloud есть живое и растущее serverless-комьюнити Yandex Serverless Ecosystem, где можно задавать вопросы и оперативно получать ответы от единомышленников и коллег. Присоединяйтесь!

И ещё в Yandex.Cloud действует программа free tier. Это позволяет реализовать массу проектов бесплатно, если не выходить за лимиты.

П.С. Кстати, новый сервис экосистемы Serverless Yandex Data Streams вышел в стадии Preview. С помощью Data Streams вы можете настраивать потоки данных, их обработку и поставку данных в системы хранения Yandex.Cloud без написания кода. Подробнее можно почитать тут.

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


  1. Recosh
    18.08.2021 12:04
    -1

    ZeroMQ, почему о нём никто не говорит? В несколько строк можно повторить и тот же PUB/SUB, и хранилище ключ значение и на родном для разработчика языке и балансировку. Полезно когда не знаешь что тебе подойдёт, сделай свое :D

    В своём проекте написал модуль на python который импортирую в микросервисы и все работает сообща


    1. Xop
      18.08.2021 14:09
      +2

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


      1. Recosh
        18.08.2021 15:02

        Эти возможности при необходимости описываются при обёртке брокера и клиента над ZeroMQ. Ну в общем вероятно Вы правы.


    1. murzilka
      19.08.2021 09:25

      Тогда уж nanomsg, более продвинутая версия от того же автора


  1. darkit
    18.08.2021 13:20

    Странно почему для SQS стоит галочка easy installation а для Кафки нет. Ведь можно взять AWS MSK и там все так же будет изи :)


    1. KAndy
      18.08.2021 20:56

      Kak, впрочем, и RabbitMQ как часть AWS MQ https://aws.amazon.com/ru/about-aws/whats-new/2020/11/announcing-amazon-mq-rabbitmq/


  1. derikn_mike
    18.08.2021 15:54
    +2

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


    1. golodnyj Автор
      19.08.2021 09:54

      Кафку самостоятельно поднимаете или используете менеджет решение?


      1. derikn_mike
        21.08.2021 13:45
        +1

        на докерах разворачиваем в пару кликов


  1. lasc
    19.08.2021 07:22
    +1

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


  1. rsvasilyev
    19.08.2021 12:12
    +5

    Если задача - доставить сообщение первому попавшемуся потребителю - RabbitMQ подойдет хорошо.
    Пример использования: разгребание и отправка email-ов воркерами в фоне.

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

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

    Буквально недавно завезли RabbitMQ Streams, который вроде бы решает этот вопрос, но там, похоже, как минимум нет consumer groups.