Введение
Современные цифровые экосистемы, объединяющие множество сервисов, в которые могут входить мобильные приложения по доставке еды, интернет-банки, аптеки, службы такси и т. д., не могут функционировать без интенсивного обмена пользовательскими метриками. Данные о пользователях, их действиях и просмотрах могут иметь такой же уровень важности, как и технические параметры работы самой системы. Такие данные критически важны не только для работоспособности основных функций онлайн-сервисов, но и для последующей аналитики, персонализации, мониторинга и т. д. Новые ИС часто строятся с использованием микросервисной архитектуры, предпочитая её монолитной [1]. Однако в такой архитектуре микросервисы будут должны обмениваться данными друг с другом, в том числе и пользовательскими метриками. Традиционный синхронный подход к обмену данными (REST/HTTP) в высоконагруженной среде демонстрирует свою несостоятельность. Временная недоступность сервиса-получателя или всплески трафика могут приводить к безвозвратной потере метрик, а синхронное взаимодействие между микросервисами может являться причиной меньшей пропускной способности всего приложения, по сравнению с аналогичным вариантом с использованием асинхронного взаимодействия.
Актуальность данного исследования обусловлена необходимостью поиска архитектурных решений, гарантирующих надежность обмена данными в условиях постоянно растущих нагрузок в распределенных системах. Целью работы является исследование применения событийно-ориентированной архитектуры (EDA) для создания отказоустойчивого конвейера передачи пользовательских метрик.
Событийно-ориентированная архитектура (EDA) внутри микросервисной
Событийно-ориентированная архитектура является архитектурным стилем, в котором взаимодействие между компонентами системы строится вокруг событий. Чаще всего события представляют собой команду на выполнение какого-либо действия, совершившийся факт или изменение состояния сущности.
Основная идея событийно-ориентированной архитектуры заключается в том, что компоненты не вызывают друг друга напрямую (синхронно), а генерируют события, которые получают другие компоненты системы и реагируют на них асинхронно. Это обеспечивает слабую связанность и высокую гибкость системы. Компоненты системы в EDA разделяются на производителей событий и их потребителей. Производители генерируют события и публикуют их в специальный канал или брокер сообщений. В реализации некоторых брокеров опубликованное событие, полученное одним клиентом, становится недоступно другим. В других же несколько потребителей могут читать один поток сообщений, не мешая друг другу. Потребители же являются компонентами системы, которые подписаны на определённые типы событий и реагируют на них. Некоторые реализации брокеров позволяют иметь несколько потребителей для одного типа события.
Использование EDA архитектуры само по себе не решает проблемы обмена данными между компонентами системы. Данную архитектуру можно реализовать и используя БД [2] или имитировать её реализацию через REST взаимодействие, используя долгие опросы или функции обратного вызова. Однако в большинстве реализаций событийно-ориентированной архитектуры центральным элементом инфраструктуры выступает брокер сообщений, который совместно с некоторыми паттернами рассмотренными далее позволяют решить поставленные проблемы.
Брокеры сообщений
Брокер представляет собой промежуточное программное обеспечение, которое выступает в роли посредника между производителями и получателями событий. Его внедрение позволяет достичь одной из ключевых целей EDA – слабой связанности компонентов. Используя, брокер, сервисы не потребуют доступности контрагента в момент направления запроса к нему или знания о его сетевом расположении.
Брокер принимает входящие сообщения от производителей и сохраняет их. Это позволяет системе переживать пиковые нагрузки и временные отказы потребителей за счет буферизации. Сообщения не теряются, а ожидают своей обработки в очереди или логе. После получения сообщения брокер определяет, какому именно потребителю или группе потребителей должно быть доставлено каждое конкретное сообщение. Маршрутизация осуществляется на основе правил, привязанных к каналам коммуникации, которые в разных реализациях могут называться топиками, очередями, ключами маршрутизации и т. д. Также брокер хранит реестр подписчиков, отслеживая, какие именно потребители обязаны получать те или иные типы событий, и управляет состоянием этих подписок.
Брокеры могут реализовать различные политики гарантий доставки сообщений. В них входят:
- At most once. Сообщения с такой политикой будут доставлены не более одного раза;
- At least once. Сообщения с такой политикой доставки будут доставлены по крайней мере один раз;
- Exactly once. Сообщения с такой политикой доставки будут доставлены строго один раз.
Выбор политики оказывает большое влияние на производительность и сложность системы и зависит от критичности доставки данных.
Реализации современных брокеров основываются либо на очереди сообщений, либо на логи. Брокеры на основании очередей сообщений реализуют классическую модель обмена сообщениями, часто соответствующую спецификациям Java Message Service и Advanced Message Queuing Protocol. Примерами таких брокеров являются RabbitMQ, ActiveMQ, IBM MQ, WebSphere MQ и другие [3, 4]. Такие брокеры обеспечивают мощную и гибкую маршрутизацию и хорошо подходят для сценариев, где требуется сложная логика распределения задач или надежная доставка команд. Однако часто используемая в них модель удаления сообщений после подтверждения их получения делает сложным повторное воспроизведение исторических данных или подключение нового потребителя для анализа уже прошедших событий.
В основе брокеров на основании логов лежит концепция лога – неизменяемой, упорядоченной последовательности записей. Производители записывают события в конец лога. События не удаляются после прочтения, а хранятся в течение заданного периода времени или до достижения определенного объема лога. Потребители читают события из лога, самостоятельно управляя своим смещением в нём. Ключевым представителем такого подхода является Apache Kafka. Лог-ориентированные брокеры обеспечивают исключительно высокую пропускную способность, в том числе и в сравнении с брокерами на основании очередей сообщений и поддерживают возможность обращаться к истории событий [5 , с. 42].
Для высоконагруженного обмена пользовательскими метриками Apache Kafka станет отличным выбором благодаря высокой пропускной способности, устойчивости к сбоям и возможности подключения как нескольких производителей, так и нескольких потребителей к одному каналу сообщений [5]. Однако, при работе с брокером сообщений часто будет возникать проблема распределённой транзакции. Например, при обработке какого-то действия пользователя нужно будет обработать это действие, информацию о нём сохранить в БД и отправить обновлённую метрику пользователя в брокер сообщений. Если выполнять отправку обновлённой метрики во время открытой транзакции с её обновлением в БД, то это окажет сильное влияние на пропускную способность приложения. Транзакции в БД станут долгими, а выполнять по сетевому запросу к брокеру на каждое сообщение может оказаться слишком расточительным. Лучше объединять сообщения в пачки и отправлять целые пачки в брокер, но тогда отправка обновления метрики будет за пределами транзакции на её обновление в БД и потеряется атомарность обработки действия пользователя. Так можно успешно завершить транзакцию на обновление в БД, а при возникновении сетевой недоступности или других проблем не отправить обновление метрики в брокер. Также возможно ситуация, когда обновлённая метрика отправится в kafka, но транзакция в БД откатится. Одним из вариантов решения этих проблем является паттерн Transaction Outbox.
Transactional Outbox
Вместо того чтобы отправлять сообщение с обновлённой метрикой пользователя напрямую в Kafka в той же транзакции, в которой происходит обработка действия пользователя, в паттерне transactional outbox предлагается сохранять готовое сообщение для отправки в специальную таблицу (outbox) в той же локальной транзакции БД, в которой происходит обработка бизнес-данных. Таким образом, атомарность обновления в БД и отправки в Kafka обеспечивается на уровне базы данных. Либо будет выполнена обработка бизнес-данных в БД и будет подготовлено и сохранено сообщение для отправки в Kafka, либо не сохранится ничего. После фиксации транзакции отдельный асинхронный процесс читает записи из outbox-таблицы и отправляет их брокеру Kafka [6]. Такой механизм помимо обеспечения атомарности обработки даёт возможность использования батчевой отправки подготовленных сообщений брокеру кафка из БД для улучшения пропускной способности механизма отправок и ожидания подтверждения получения сообщений от kafka кластера. Когда отправка подтверждена сообщения подтверждена, запись в outbox таблице помечается как обработанная или удаляется.
Стоит отметить, что есть и другие инструменты решения проблемы распределённых транзакций, например, eXtended Architecture спецификация. Kafka поддерживает распределённые транзакции с полноценными ACID-гарантиями только в пределах самой себя, т. е. транзакция может охватывать несколько производителей и потребителей для разных топиков. Однако Kafka не реализует XA-протокол, поэтому не поддерживает распределённые транзакции с участием ресурсов других типов, например СУБД или JMS.
Заключение
Итогом применения всех рассмотренных инструментов будет переход от архитектуры взаимодействия по REST (рис. 1) к архитектуре взаимодействия по Kafka (рис. 2).

Рис. 1. Обмен метриками пользователя через синхронные REST запросы

Рис. 2. Обмен метриками пользователя через Kafka с Transactional outbox
Внедрение предлагаемых инструментов позволяет минимизировать потери данных при высоконагруженном обмене пользовательскими метриками и обеспечить стабильность цифровой экосистемы в целом. В контексте современной микросервисной архитектуры EDA становится естественным выбором для организации взаимодействия между независимо развёртываемыми сервисами, позволяя им обмениваться информацией без жёстких синхронных зависимостей. Использование Kafka при реализации EDA совместно с Transactional Outbox позволяет отказаться от синхронного межсервисного взаимодействия, не только не теряя гарантий доставки сообщений до контрагентов, но и повышая отказоустойчивость системы в целом по сравнению с синхронным межсервисным взаимодействии через REST.
.png&w=384&q=75)
.png&w=640&q=75)