Серия по проектированию систем Последовательное руководство по созданию высокопроизводительных систем потоковой передачи данных с нуля!

Серия по проектированию систем пошаговое руководство для создания высокопроизводительных систем потоковой передачи данных с нуля!

Источник: Unsplash

Настройка примера проблемы: система рекомендаций

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

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

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

Изображение автора

Эти события в основном говорят нам о пользователе. Например, пользователь, заинтересованный в покупке игрового ПК, также может быть заинтересован в игровой клавиатуре или мыши.

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

Изображение автора

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

Рекомендации обычно являются последним моментом во многих системах, но они намного важнее, чем вы можете подумать. Почти все приложения, которыми вы пользуетесь, в значительной степени полагаются на подобные рекомендательные сервисы для стимулирования действий пользователя. Например, согласно этой статье, 35% интернет-продаж на Amazon были сгенерированы через их рекомендуемые товары.

Однако проблема заключается в огромном объеме данных. Даже если мы работаем только с умеренно популярным веб-сайтом, мы все равно можем получать сотни тысяч событий в секунду (возможно, даже миллионы) в пиковое время! И если появляется новый продукт или большая распродажа, это число может быть гораздо выше.

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

Что такое потоковая обработка данных?

Потоковая обработка данных – это то, о чем я описал выше. Это система, которая включает непрерывную передачу данных (например, события), выполняет несколько шагов обработки и сохраняет результаты для последующего использования.

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

Цель этой статьи – понять, как обрабатывать такой объем данных, как их принимать, обрабатывать и сохранять для последующего использования, а не понимать фактическую логику этапов обработки (но мы все равно немного погрузимся в нее для развлечения).

Создание потокового конвейера данных: пошаговое руководство

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

Прием данных

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

Картинка от автора

Но хотя проблема кажется простой на первый взгляд, она имеет свою долю тонкостей,

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

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

  1. Сервисы, отправляющие события, не должны ожидать ответа от нашего сервиса рекомендаций. Это увеличит задержку в сервисах и заблокирует их до тех пор, пока сервис рекомендаций не отправит им 200. Вместо этого они должны отправлять запросы fire-and-forget (отправить и забыть).
  2. Количество событий будет сильно меняться, возрастая и уменьшаясь в течение дня (например, возрастать вечером или во время распродажи), поэтому нам придется масштабировать наш сервис рекомендаций в зависимости от объема событий. Это необходимо управлять и рассчитывать.
  3. Если наш сервис рекомендаций выйдет из строя, то мы потеряем события, пока он не будет работать. В этой архитектуре наш сервис рекомендаций является единой точкой отказа.

Давайте исправим это, используя брокер сообщений или «платформу потоковых событий», такую ​​как Apache Kafka. Если вы не знаете, что это такое, это просто инструмент, который вы настраиваете для приема сообщений от «издателей» в определенные темы. «Подписчики» слушают или подписываются на тему, и когда сообщение публикуется в этой теме, подписчик получает сообщение. Мы более подробно поговорим о темах Kafka в следующем разделе.

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

Картинка от автора

Таким образом, каждый сервис отправляет события в Apache Kafka. Сервис рекомендаций получает эти события от Kafka. Давайте посмотрим, как это помогает нам –

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

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

Обработка данных

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

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

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

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

Давайте предположим, что мы можем представить каждого пользователя (или его/ее интересы) как точку в двумерном пространстве. Каждая ось представляет характеристику нашего пользователя. Предположим, что ось X представляет, насколько он/она любит путешествовать, и ось Y представляет, насколько он/она увлекается фотографией. Каждое действие пользователя влияет на положение этого пользователя в двумерном пространстве.

Допустим, пользователь начинает с следующей точки в нашем двумерном пространстве —

Изображение от автора

Когда пользователь ищет “путешественную сумку”, мы двигаем точку вправо, так как это указывает на то, что пользователю нравятся путешествия.

Изображение от автора

Если бы пользователь искал камеру, мы бы переместили пользователя вверх по оси Y.

Мы также представляем каждый продукт как точку в том же двумерном пространстве,

Изображение от автора

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

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

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

Вернемся к нашей проблеме. Для каждого события нам нужно обновлять представления пользователей (перемещать пользователя на нашей n-мерной диаграмме) и возвращать связанные продукты как рекомендации.

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

  1. update-embeddings: Обновить представления пользователей
  2. gen-recommendations: Получить связанные с представлениями пользователей продукты
  3. save: Сохранить созданные рекомендации и события

Мы можем создать службу Python для каждого типа события.

Изображение от автора

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

Изображение от автора

Поскольку мы снова используем Kafka вместо отправки запросов, эта архитектура дает нам все преимущества, о которых мы обсуждали ранее. Ни одна одиночная Python микрослужба не является единой точкой отказа, и намного проще управлять масштабированием. Последняя служба save-worker должна сохранять рекомендации для будущего использования. Давайте посмотрим, как это работает.

Счетчики данных

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

  1. Масштабируемость и высокая производительность записи – Помните, у нас есть много входящих событий, и каждое событие также обновляет рекомендации пользователя. Это означает, что наше хранилище данных должно иметь высокую производительность записи. Наша база данных должна быть высокомасштабируемой и способной масштабироваться линейно.
  2. Простые запросы – Мы не собираемся выполнять сложные JOIN-ы или делать разные типы запросов. Наши потребности в запросе относительно просты: для заданного пользователя вернуть список предварительно вычисленных рекомендаций
  3. Нет требований ACID – Нашей базе данных не нужно иметь строгое соответствие ACID. Она не нуждается в гарантиях согласованности, атомарности, изоляции и долговечности.

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

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

Мы можем использовать две таблицы: одну для хранения рекомендаций для каждого пользователя и другую для хранения событий. Последний Python микросервис save сохранит данные о событии и рекомендациях в Cassandra.

Изображение автора

Запросы данных

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

Изображение автора

Полная архитектура

И вот и все! Мы завершили всю архитектуру, давайте нарисуем полную архитектуру и посмотрим, как она выглядит.

Изображение автора

Для дальнейшего обучения

Kafka

Kafka – удивительный инструмент, разработанный LinkedIn для обработки экстремального масштаба (эта статья на блоге LinkedIn в 2015 году говорит о ~13 миллионах сообщений в секунду!).

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

Я написал статью, в которой я объяснил, что такое Kafka, в чем отличие от брокеров сообщений и отрывки из оригинальной статьи о Kafka, написанной инженерами LinkedIn. Если вам понравилась эта статья, ознакомьтесь с моей статьей о Kafka –

Серия о проектировании систем: Apache Kafka с высоты 10 000 футов

Давайте посмотрим, что такое Kafka, как оно работает и когда следует его использовать!

betterprogramming.pub

Cassandra

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

Решения системного дизайна: когда использовать Кассандру и когда нет

Все, что вам нужно знать о том, когда использовать Кассандру и когда нет

VoAGI.com

Рекомендательные системы

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

Я довольно много пишу о системах поиска, и я немного коснулся того, как создать базовую персонализацию в системах поиска, но моя следующая тема будет глубже углубиться во все тонкости рекомендательных систем, как они работают, и как их архитектурировать. Если вам это интересно, следите за мной на VoAGI для получения большего контента! Я также регулярно публикую короткие материалы в LinkedIn для чтения, например, в этом посте про Kafka Connect, который описывает, как он работает, и почему он настолько популярен с помощью простой диаграммы.

Заключение

Надеюсь, вам понравился этот пост, если у вас есть отзывы о посте или мысли о том, о чем я должен поговорить дальше, вы можете оставить комментарий!