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

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

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

Что такое обогащение? Вкратце, это реализация расширения исходных событий для удовлетворения новых требований к функциям.

Описание функции

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

  1. Кто меня посещает
  2. Другие также просматривают
  3. Кто меня просматривает

Первая характеристика — сколько человек просмотрели мой профиль за определенный период времени. Например, сколько раз мой профиль просмотрели за неделю.

Вторая характеристика — это расширенная версия предыдущей характеристики: кто еще из этих людей просматривал мой профиль? Например, 10 человек просмотрели мой профиль за неделю, и они также просмотрели Элона Маска.

Третья функция является производной от первой. Какова личность этих людей, которые просматривали меня? Например, за неделю мой профиль просмотрели 10 человек, и трое из них были сотрудниками Google.

Кто меня посещает & Другие тоже просматривают

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

{
    eventType: "PageView",
    timestamp: 1660270009,
    viewerId: 1234,
    viewedId: 5678
}
Вход в полноэкранный режим Выход из полноэкранного режима

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

На приведенной выше схеме, предположим, мой идентификатор пользователя 4567, тогда из потока событий легко найти двух пользователей, которые просматривали меня, это 2345 и 1234.

Далее из записи потока мы также можем понять, что 2345 просмотрел 1234, а 1234 просмотрел 5678 и 2345.

Собрав эту информацию вместе, мы можем ответить на вопрос.

  1. Кто меня посещает? 2345 и 1234.
  2. Другие также просматривают 1234, 5678 и 2345.

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

Какая личность меня просматривает?

Однако не так просто узнать, какая личность просматривает мой профиль.

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

Добавление метаданных в события

Давайте добавим задание в исходное событие. При просмотре пользователем, помимо отправки события с id, необходимо также прикрепить задание.

{
    eventType: "PageView",
    timestamp: 1660270009,
    viewerId: 1234,
    viewedId: 5678,
    viewerJob: "Google"
}
Вход в полноэкранный режим Выход из полноэкранного режима

Кажется, что проблема решена, но так ли это на самом деле?

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

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

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

Поэтому мы знаем, что это не лучший подход.

Запрос из внешних хранилищ данных

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

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

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

Такая реализация вполне подходит для архитектуры очередей сообщений. Однако в потоковой архитектуре такая реализация создает узкое место в производительности. Допустим, потоковая структура имеет среднюю пропускную способность более чем в десять раз выше, чем база данных. Если бы каждое событие должно было полагаться на внешние источники данных, общая пропускная способность стала бы 1/10.

Кроме того, существует проблема, связанная с такой архитектурой.

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

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

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

Слияние событий

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

В этом примере мы выбрали Apache Samza в качестве иллюстрации, но на самом деле есть много хороших альтернатив, например Apache Flink.

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

Это событие модификации может либо поступать из Change Data Capture (CDC) базы данных, либо событие модификации может быть отправлено из пользовательской службы, но подробности не являются темой этой статьи.

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

Когда Samza получает ProfileEdit, она может сохранить последнее состояние каждого пользователя. Получив PageView, она может извлечь необходимую информацию из сохраненного состояния и собрать новые события.

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

Хотя состояние разделяется несколькими экземплярами, все равно возможно превышение верхнего предела. Как решить проблему масштабируемости?

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

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

Заключение

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

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

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

Оцените статью
devanswers.ru
Добавить комментарий