Демонстрация дизайна системы

Аудитория

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

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

Аргумент

Сначала давайте посмотрим на нашу постановку задачи.

Система для проектирования

Мы надеемся разработать платформу видео по запросу, такую ​​​​как YouTube или Netflix. Маловероятно, что вы не сталкивались с ними раньше, но если нет, предпосылка заключается в том, что пользователь может загружать или просматривать видео в Интернете. Точные требования таковы:

  1. Мы должны иметь возможность загружать видео.
  2. Мы должны иметь возможность просматривать видео.
  3. Мы должны иметь возможность выполнять поиск по названиям видео.

Мы проигнорируем тот факт, что вы не можете загружать видео на Netflix, если вы не являетесь производственной студией. Давайте представим, что вы Квентин Тарантино.

Подход

У нас стандартный подход к проектированию системы, который более подробно описан в статье здесь. Тем не менее, шаги кратко изложены ниже:

  1. Уточнение требований: убедитесь, что у нас есть вся информация, прежде чем начать. Это может включать в себя ожидаемое количество запросов или пользователей.
  2. Задняя часть оценки конверта:Выполнение некоторых быстрых расчетов для оценки необходимой производительности системы. Например, сколько памяти или пропускной способности нам нужно?
  3. Дизайн интерфейса системы: как наша система будет выглядеть снаружи, как люди будут с ней взаимодействовать? Как правило, это контракт API.
  4. Дизайн модели данных. Как будут выглядеть наши данные при их хранении. На этом этапе мы могли бы подумать о реляционных и нереляционных моделях.
  5. Логический дизайн:собираем все вместе в грубую систему! В этот момент я думаю на уровне «как бы я объяснил свою идею тому, кто ничего не знает о технологиях?»
  6. Физический дизайн. Теперь мы начинаем думать о серверах, языках программирования и деталях реализации. Мы можем наложить их поверх логического дизайна.
  7. Выявление и устранение узких мест: На этом этапе у нас будет работающая система! Теперь дорабатываем дизайн.

С учетом сказанного, давайте застрянем!

Уточнение требований

Давайте подумаем о некоторых начальных вопросах. Изначально мне было интересно, каковы максимальный и средний размеры видео. Затем я буду думать о количестве пользователей и их коэффициенте чтения/записи/поиска.

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

Задняя часть оценки конверта

Предположим, что у нас есть 100 миллионов пользователей (на самом деле, возможно, больше!), ежедневно читающих и пишущих в соотношении 100:1. Средний размер файла составляет 1 ГБ для HD, а максимальный размер файла — 50 ГБ (не имеет значения, насколько они точны).

Это означает, что у нас будет приблизительная емкость 100,000,000 * 1GB = 100PB/d только для загрузки! Мы можем вычислить это в запросах в секунду, учитывая среднюю продолжительность видео, но можно с уверенностью сказать, что мы имеем дело с большим количеством данных!

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

Дизайн системного интерфейса

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

  1. Загрузить видео
  2. Скачать видео
  3. Поиск

Загрузка видео будет осуществляться через HTTP. Одна вещь, о которой нам нужно знать, это размер файла. В другом сценарии мы можем использовать FTP, но, учитывая, что у нас есть случайные пользователи в Интернете, это не имеет особого смысла.

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

Оттуда мы могли бы использовать API-интерфейсы JavaScript File и Blob, чтобы разрезать видеофайл и отправлять частичные запросы с заголовком Content-Range, представляющим диапазон байтов, который представляет этот запрос. Наш тип контента будет application/octet-stream, а за повторную сборку частей отвечает сервер.

Это означает, что нам нужна конечная точка для создания файла, POST в конечной точке /file, которая создает новый файловый объект с метаданными, а затем еще одна конечная точка в POST file/{id}/chunk, которая позволяет пользователю опубликовать фрагмент.

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

Загрузка может использовать запрос диапазона HTTP. Это позволяет нам запрашивать части видео за раз (возвращая 206), то есть мы можем динамически запрашивать сегменты видео по мере необходимости. Использование GET file/{id}/chunk с заголовком Content-Range может быть одним из решений.

К счастью, поиск немного проще, мы можем от GET до /search?title=<search title> получить обычные коды ответов.

Дизайн модели данных

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

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

Файл

id               BIGINT    PRIMARY KEY 
title            VARCHAR

Теперь нам нужно подумать о наших файловых фрагментах. Помните, что нам нужно обслуживать множество сетей и устройств! Это означает, что мы будем хранить фрагменты во множестве файлов различного качества и форматов. С появлением хранилища объектов, такого как AWS S3, мы можем указать, где в S3 находится наш файл.

Фрагмент

id           BIGINT    PRIMARY KEY 
file_id      BIGINT    FOREIGN KEY REFERENCES file(id)
format       VARCHAR
quality      BIGINT
order        BIGINT    
location     VARCHAR

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

Это формирует ядро ​​нашей модели данных. Давайте перейдем к логическому дизайну!

Логический дизайн

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

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

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

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

  • Транскодирование: создание файла разных размеров.
  • Кодирование: повторное кодирование файла в другом формате.

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

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

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

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

Функция поиска должна быть сравнительно простой. Файловая служба добавит метаданные файла в поисковую систему, поэтому поисковая служба отвечает за преобразование URL-запроса во что-то, что мы можем использовать с поисковой системой, а затем упорядочивание ответа.

Физический дизайн

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

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

Изначально все наши статические сервисы используют AWS ECS. Есть аргумент, что мы могли бы полностью отказаться от сервера, но я чувствую, что мы не хотим беспокоиться о времени холодного запуска. Наш шлюз — это AWS API Gateway, а наши очереди используют SQS.

Мы храним наши фрагменты в S3 и используем AWS Elemental MediaConvert для нашего кодирования. Сама поисковая система — Amazon OpenSearch.

Выявление и устранение узких мест

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

На диаграмме видно, что наш API-шлюз теперь записывает в Kinesis stream, который, в свою очередь, отправляет в AWS EMR (вероятно, с включенной Spark Streaming) для обработки и повторного разделения наших файлов.

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

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

Заключительная часть оптимизации — добавление CDN. Поскольку у нас будут пользователи по всему миру, мы не хотим, чтобы все они пытались получить доступ к своим видео с серверов в Великобритании. Имеет смысл попытаться распространять наш контент ближе к тому месту, где они находятся. Обратите внимание, у Netflix на самом деле есть собственное решение этой проблемы под названием Open Connect, на которое стоит обратить внимание!

Заключение

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

Вот несколько примеров:

  • Zuul: Шлюз приложений.
  • Hystrix (устарело): автоматический выключатель.
  • Эврика: сервисное открытие.
  • Feign: биндер HTTP-клиента.

Проверь их!