Лекция 13

01.01.0001

Лекция 13. Введение в межсервисное взаимодействие

Микросервисная архитектура

Микросервисная архитектура позволяет:

  • Разделять сервис на отдельные функции
  • Независимо масштабировать отдельные части
  • Обеспечивать повышенную устойчивость к сбоям
  • Использовать разные технологии под разные задачи

Но переход от монолита к микросервисам — сложный процесс. Самый трудный этап — изменение механизма взаимодействия внутренних компонентов.

Монолит vs Микросервисы: взаимодействие

АрхитектураВзаимодействие
МонолитнаяКомпоненты обращаются друг к другу на уровне кода или функции в рамках одного процесса
МикросервиснаяМодули взаимодействуют через сеть по протоколонезависимой технологии

Прямое преобразование внутрипроцессных вызовов в удалённые не подойдёт распределённым средам.

Сложность перехода на микросервисы

Унифицированного решения нет — для каждой ситуации нужно своё. Варианты:

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

Типы связи: синхронная vs асинхронная

ТипОписаниеПример
АсинхронныйОтправитель не ждёт ответ. Сообщения передаются в очередь брокераAMQP (Advanced Message Queue Protocol)
СинхронныйПри отправке клиент ждёт ответ. Задачи выполняются только после ответа сервераHTTP

Брокеры сообщений: ActiveMQ и Kafka

Книги в этой области сравнивают и противопоставляют две популярные технологии:

  • Apache ActiveMQ
  • Apache Kafka

Система обмена сообщениями

Для общения двух приложений нужно:

  1. Определить интерфейс:
    • Выбрать транспорт/протокол
    • Согласовать форматы сообщений
  2. Стандартизировать через схему (XML, JSON) или неформальное соглашение

Пока формат сообщений и порядок их отправки согласованы — внутренности систем могут меняться. Эти системы расцеплены интерфейсом.

Роль посредника

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

Модель Point-to-Point (точка-точка)

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

Эта модель реализуется через очереди:

  • Очередь — буфер FIFO
  • На неё может подписаться один или несколько потребителей
  • Каждое сообщение доставляется только одному из подписанных потребителей
  • Сообщения распределяются справедливо между потребителями

Надёжность vs Персистентность

  • Надёжность (durability) — система сохраняет сообщения при отсутствии подписчиков до тех пор, пока потребитель не подпишется
  • Персистентность (persistence) — система записывает сообщение в хранилище между получением и отправкой потребителю

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

Когда использовать Point-to-Point

Когда требуется однократное действие: внесение средств на счёт, выполнение заказа. Очереди обеспечивают в лучшем случае доставку хотя бы один раз (at-least-once).

Модель Publisher-Subscriber (Pub/Sub)

Аналогия: Габриэлла набирает номер конференции. Пока она подключена — слышит всё, что говорит спикер. Когда отключается — пропускает. При повторном подключении продолжает слышать.

Эта модель реализуется через топики:

  • Сообщение, отправленное в топик, распределяется по всем подписанным пользователям
  • Топики обычно ненадёжные (nondurable) — подписчики пропускают сообщения, отправленные пока они оффлайн
  • Гарантия: доставка не более одного раза (at-most-once) для каждого потребителя

Когда использовать Pub/Sub

Когда сообщения информационные, и потеря одного — не критична. Пример: температурные датчики передают показания раз в секунду. Если одно сообщение пропущено — следующее придёт скоро.

Гибридные модели

Сценарии часто требуют совмещения моделей:

  • Несколько систем нуждаются в копии сообщения (как Pub/Sub)
  • Но требуется надёжность и персистентность (как Point-to-Point)

Решение: адресат (общий термин для очередей и топиков) распределяет сообщения как топик, но каждая система может иметь несколько потребителей, как в очереди. Гарантия — один раз на каждую заинтересованную сторону.

Гибридные модели применяются:

  • В ActiveMQ — через виртуальные или составные адресаты
  • В Kafka — неявно, как фундаментальное свойство дизайна адресата

ActiveMQ

ActiveMQ — классическая система обмена сообщениями, написанная в 2004 году. Восполняла потребность в open-source брокере сообщений.

ActiveMQ и JMS

ActiveMQ разработана как реализация спецификации JMS (Java Message Service):

  • JMS описывает абстракции для отправки/получения сообщений в асинхронном режиме
  • JMS включает чёткие указания по обязанностям клиента и брокера
  • Сама связь клиент-брокер исключена из спецификации — чтобы существующие брокеры могли стать JMS-совместимыми

ActiveMQ свободна в определении своего протокола — OpenWire. Используется в реализациях JMS, .NET (NMS) и C++ (CMS).

Протоколы

AMQP (Advanced Message Queue Protocol)

ISO/IEC 19464:2014. Не путать с предшественником 0.x, который реализован в RabbitMQ (0.9.1).

AMQP 1.0:

  • Двоичный протокол общего назначения
  • Нет понятий клиентов или брокеров
  • Поддерживает управление потоками, транзакции, QoS:
    • Не более одного раза (at-most-once)
    • Не менее одного раза (at-least-once)
    • Точно один раз (exactly-once)

MQTT (Message Queuing Telemetry Transport)

ISO/IEC 20922:2016. Лёгкий Pub/Sub-протокол. Используется для:

  • Приложений «Машина-Машина» (M2M)
  • Интернет вещей (IoT)

ActiveMQ поддерживает оба протокола.

JMS API

JMS определяет набор программных интерфейсов:

ConnectionFactory → Connection → Session
                                    │
                                    ├── MessageProducer
                                    ├── MessageConsumer
                                    └── Message

ConnectionFactory

  • Интерфейс верхнего уровня для установления соединений
  • В типичном приложении — единственный экземпляр (Singleton)
  • В ActiveMQ — ActiveMQConnectionFactory
  • Сообщает местонахождение брокера и низкоуровневые детали

Connection

  • Долгоживущий объект, похожий на TCP-соединение
  • Существует в течение всего жизненного цикла приложения
  • Потокобезопасный — может работать с несколькими потоками одновременно
  • Через него создаются объекты Session

Session

  • Дескриптор потока при взаимодействии с брокером
  • НЕ потокобезопасный — не может быть доступен нескольким потокам одновременно
  • Основной транзакционный дескриптор: commit и rollback
  • Через него создаются Message, MessageConsumer, MessageProducer, получаются ссылки на Topic и Queue

MessageProducer и MessageConsumer

  • MessageProducer — отправляет сообщения адресату
  • MessageConsumer — получает сообщения, два механизма:
    • MessageListener — реализуемый интерфейс обработчика, последовательно обрабатывает сообщения по мере поступления (один поток)
    • Polling — опрос через метод receive()

Message

Message — самая важная структура, переносит данные. Состоит из двух частей:

  1. Метаданные — заголовки и свойства
  2. Тело сообщения

Заголовки — хорошо известные элементы, определённые спецификацией JMS: JMSDestination, JMSTimestamp и т.д.

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

Типы тела сообщения:

  • TextMessage — для строк
  • BytesMessage — для двоичных данных

Модель обмена сообщениями в ActiveMQ

Полезная (хоть и неточная) модель — «две половинки мозга»:

  • Одна часть отвечает за приём сообщений от продюсера
  • Другая отправляет сообщения потребителям

В реальности отношения сложнее из-за оптимизаций производительности, но модель достаточна для базового понимания.

Процесс отправки сообщения

  1. Маршалинг. Отправляющий поток вызывает клиентскую библиотеку, маршализирует сообщение в нужный формат. Сообщение отправляется брокеру.
  2. Запись в хранилище. Брокер записывает сообщение в своё внутреннее хранилище.
  3. Подтверждение записи. Персистенс-адаптер получает подтверждение записи. Это самая медленная часть взаимодействия.
  4. Ответ клиенту. Брокер отправляет клиенту подтверждение. Поток клиента может продолжить работу.

Publisher Confirmations

Если у брокера закончилась память или диск:

  • Брокер приостанавливает операцию отправки, заставляя продюсер ждать (Producer Flow Control)
  • ИЛИ отправляет негативное подтверждение продюсеру (через исключение)

В простом сценарии используется паттерн Fire-and-forget — записали и забыли.

Buffering

Когда брокер записывает данные на диск, он взаимодействует с файловой системой. Файловая система буферизует данные — минимизирует количество операций записи.

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

Уровни кэширования:

  1. Буферный кэш файловой системы
  2. Кэш контроллера диска (аппаратный уровень)

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

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

  1. Потребитель выражает готовность принимать сообщения (через MessageListener или receive()).
  2. ActiveMQ постранично читает (pages) сообщения из хранилища в память.
  3. Сообщения перенаправляются (dispatched) консумеру, часто частями для снижения сетевого взаимодействия.
  4. Сообщения помещаются в prefetch buffer (буфер предварительной выборки) у консумера — выравнивает поток сообщений.
  5. Логика приложения вычитывает сообщения из буфера и отправляет подтверждение брокеру.
  6. После получения подтверждения брокером сообщение удаляется из памяти и хранилища.

Термин «удаление» вводит в заблуждение — в журнал записывается запись о подтверждении и увеличивается указатель в индексе. Фактическое удаление выполняется сборщиком мусора в фоновом потоке.

Курсорный механизм

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

Используемый протокол согласования (coherency) — значительная часть того, что делает механизм диспетчеризации ActiveMQ отличным от Kafka.