Лекция 13. Введение в межсервисное взаимодействие
Микросервисная архитектура
Микросервисная архитектура позволяет:
- Разделять сервис на отдельные функции
- Независимо масштабировать отдельные части
- Обеспечивать повышенную устойчивость к сбоям
- Использовать разные технологии под разные задачи
Но переход от монолита к микросервисам — сложный процесс. Самый трудный этап — изменение механизма взаимодействия внутренних компонентов.
Монолит vs Микросервисы: взаимодействие
| Архитектура | Взаимодействие |
|---|---|
| Монолитная | Компоненты обращаются друг к другу на уровне кода или функции в рамках одного процесса |
| Микросервисная | Модули взаимодействуют через сеть по протоколонезависимой технологии |
Прямое преобразование внутрипроцессных вызовов в удалённые не подойдёт распределённым средам.
Сложность перехода на микросервисы
Унифицированного решения нет — для каждой ситуации нужно своё. Варианты:
- Изоляция бизнес-значимых микросервисов. Внутренние микросервисы взаимодействуют асинхронно. Вызовы группируются, данные агрегируются и только потом отправляются клиенту.
- Архитектура с частично зависимыми, но согласованными микросервисами. Каждый микросервис имеет свои данные и логику.
Типы связи: синхронная vs асинхронная
| Тип | Описание | Пример |
|---|---|---|
| Асинхронный | Отправитель не ждёт ответ. Сообщения передаются в очередь брокера | AMQP (Advanced Message Queue Protocol) |
| Синхронный | При отправке клиент ждёт ответ. Задачи выполняются только после ответа сервера | HTTP |
Брокеры сообщений: ActiveMQ и Kafka
Книги в этой области сравнивают и противопоставляют две популярные технологии:
- Apache ActiveMQ
- Apache Kafka
Система обмена сообщениями
Для общения двух приложений нужно:
- Определить интерфейс:
- Выбрать транспорт/протокол
- Согласовать форматы сообщений
- Стандартизировать через схему (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 — самая важная структура, переносит данные. Состоит из двух частей:
- Метаданные — заголовки и свойства
- Тело сообщения
Заголовки — хорошо известные элементы, определённые спецификацией JMS: JMSDestination, JMSTimestamp и т.д.
Свойства — произвольные фрагменты информации, упрощающие обработку или маршрутизацию без чтения тела.
Типы тела сообщения:
- TextMessage — для строк
- BytesMessage — для двоичных данных
Модель обмена сообщениями в ActiveMQ
Полезная (хоть и неточная) модель — «две половинки мозга»:
- Одна часть отвечает за приём сообщений от продюсера
- Другая отправляет сообщения потребителям
В реальности отношения сложнее из-за оптимизаций производительности, но модель достаточна для базового понимания.
Процесс отправки сообщения
- Маршалинг. Отправляющий поток вызывает клиентскую библиотеку, маршализирует сообщение в нужный формат. Сообщение отправляется брокеру.
- Запись в хранилище. Брокер записывает сообщение в своё внутреннее хранилище.
- Подтверждение записи. Персистенс-адаптер получает подтверждение записи. Это самая медленная часть взаимодействия.
- Ответ клиенту. Брокер отправляет клиенту подтверждение. Поток клиента может продолжить работу.
Publisher Confirmations
Если у брокера закончилась память или диск:
- Брокер приостанавливает операцию отправки, заставляя продюсер ждать (Producer Flow Control)
- ИЛИ отправляет негативное подтверждение продюсеру (через исключение)
В простом сценарии используется паттерн Fire-and-forget — записали и забыли.
Buffering
Когда брокер записывает данные на диск, он взаимодействует с файловой системой. Файловая система буферизует данные — минимизирует количество операций записи.
Именно поэтому компьютер ругается на небезопасное извлечение USB — файлы могли не быть записаны.
Уровни кэширования:
- Буферный кэш файловой системы
- Кэш контроллера диска (аппаратный уровень)
ActiveMQ включает свой буфер записи — потоки записывают свои сообщения, ожидая завершения предыдущей записи. Буфер пишется одним действием, максимизируя пропускную способность.
Процесс получения сообщений
- Потребитель выражает готовность принимать сообщения (через
MessageListenerилиreceive()). - ActiveMQ постранично читает (pages) сообщения из хранилища в память.
- Сообщения перенаправляются (dispatched) консумеру, часто частями для снижения сетевого взаимодействия.
- Сообщения помещаются в prefetch buffer (буфер предварительной выборки) у консумера — выравнивает поток сообщений.
- Логика приложения вычитывает сообщения из буфера и отправляет подтверждение брокеру.
- После получения подтверждения брокером сообщение удаляется из памяти и хранилища.
Термин «удаление» вводит в заблуждение — в журнал записывается запись о подтверждении и увеличивается указатель в индексе. Фактическое удаление выполняется сборщиком мусора в фоновом потоке.
Курсорный механизм
В действительности, ActiveMQ не просто постранично читает данные, а использует механизм курсора между принимающей и перенаправляющей частями брокера для минимизации взаимодействия с хранилищем.
Используемый протокол согласования (coherency) — значительная часть того, что делает механизм диспетчеризации ActiveMQ отличным от Kafka.