Проектирование видео платформы

video platform architecture

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

Постановка задачи
Спроектировать видеоплатформу для онлайн вещания. Платформа предполагает:
1) Запись контента с различных мобильных устройств (iOS / Android смартфоны и планшеты)
2) Просмотр контента с различных устройств (MultiScreening) — iOS / Android / PC.

Важной особенностью должна стать возможность публикации на нестабильном мобильном интернете, возможность восстановления трансляции после ее обрыва, постановка трансляции на «паузу». Также есть огромное желание, чтобы контент снятый во время обрыва связи, никуда не потерялся, а появился в полной мере после ее восстановления. Это тот самый случай, когда важно чтобы «It just works» независимо от нестабильных условий связи.

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

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

Вот как это выглядит со стороны зрителя. После начала съемки, журналист публикует ссылку на видео трансляцию в twitter / facebook / G+ и т.д. Зритель получает ссылку любым путем и при желании может ее зарепостить / заретвитить и / или присоединиться к просмотру. Причем мы потратили много времени на разработку Embed видео плееров — чтобы видео отображалось прямо в социальных сетях, так зрителю будет гораздо проще присоединиться к просмотру. На наш взгляд очень эффектно смотрится live видео трансляция прямо внутри Twitter клиента.

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

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

Также необходима возможность собрать трансляцию в один файл и залить видео на YouTube или Vimeo. Это очень удобно, когда после проведения live трансляции с конференции, видео запись сразу же доступна в VOD формате. Получается довольно удобная модель использования как с точки зрения корреспондента, так и с точки зрения зрителя. Можно сделать одну длинную трансляцию о своем путешествии. Можно снимать при нестабильном коннекте и ничего не потерять. Да много в каких случаях можно использовать.

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

  • Прием потока видео трансляции — в нашем случае HLS поток генерирует мобильное приложение
  • Балансировка публикующих клиентов по нодам осуществляющим прием трансляций
  • Отказоустойчивость — возможность продолжения трансляции на любой из нод в случае отказа одной из
  • Запись — сохранение трансляции в хранилище данных
  • Хранение — отказоустойчивое распределенное хранилище видео контента
  • Генерация и обновление метаданных о трансляции — статус, длительность
  • Доставка видео — стриминг трансляций
  • Балансировка зрителей видео контента
  • Кеширование популярного контента

В целом конечно можно выделить две сущности, это трансляция видео и прием видео. Чтобы сделать видео платформу максимально простой, стабильной и легко масштабируемой, мы все видео передаем при помощи Apple HLS. Мобильные устройства транслируют видео в Apple HLS формате, видео платформа принимает и хранит чанки, зрители смотрят видео в Apple HLS (для Flash Player мы разработали HLS плагин). Получилось очень хорошо: чанки одного вида, никаких конвертаций, простота HTTP, еще немного и будет MPEG DASH 🙂

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

Поведение клиентского приложения представляет собой загрузку контента в определенный промежуток времени. Так же существует необходимость верификации публикуемого контента при получении каждого chunk’a. То есть все серверы должны знать о всех публикуемых трансляциях на данный момент. Для этого они могут либо получать информацию из внешней среды при получении каждого нового чанка, либо просто ничего не проверять и возложить это на кого-то другого. Последний вариант представляется не очень хорошей идеей. В любом случае необходим сервис который будут оповещать о поступившем контенте, после чего он займется его перемещением и координацией в СХД.

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

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

В свою очередь при failover’е на клиенте, серверу нужно будет всего 1 раз проверить статус трансляции и оповестить всех кого необходимо о том что он «перехватил» ее публикацию на себя.

Прием и балансировка публикаций
Существует множество разных методов и инструментов «балансировки», но в первую очередь нужно понять критерии балансировки. Для нас основными критериями будут являться:

  • Географическое положение пользователя на _данный_ момент
  • Загруженность наших серверов в данном регионе
  • Гарантированная доступность серверов в _данный_ момент времени

Для балансировки по регионам можно воспользоваться ELB, но это нас привязывает к AWS. Трафик там безумно дорог, трафик через ELB также оплачивается. Про все недостатки DNS Load Balancing написано уже много раз. Она подходит лишь для балансировки по «макрорегионам», где есть точка входа, которая в свою очередь дальше балансирует клиента по конечным нодам.

Другой вариант — алгоритмы на основании методов Least Connection / RR / WRR. Все эти варианты плохо подходят по той причине что у нас есть желание на некоторый период времени привязать клиента к серверу.

Выход — дополнить любой из алгоритмов выше балансировкий при помощи IP Hash и получить счастье. HAProxy позволяет сделать редирект и не гонять трафик через него самого. Но остается один немаловажный критерий — загруженность сервера. Под загруженностью я понимаю нагрузку на CPU / disk io / сетевые интерфейсы. Можно придумать какие-то сервисы и попытаться их «сдружить» с HAPproxy.

Но мы пошли другим путем. Решение для публикации представляет собой

Демон принимает информацию о пришедшем чанке от nginx модуля, осуществляет некоторые действия с ним, производит его перекладывание на СХД и обновление состояния в api портала если это нужно, обновляет метаданные по трансляции. Также этот модуль копит некоторую статистику по трансляциям идущим через этот сервер. То есть, это тот самый сервис, который осуществляет прием чанков и их «регистрацию», но дополнительно еще и собирают информацию по работе сервера.

Для балансировки публикации используется опять же C++ демон который собирает информацию со всего пула принимающих чанки серверов (recorders).

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

СХД
Какие требования предъявляются к СХД

  • не облачный сервис
  • открытое (GPL / Apache lic и т.д.)
  • распределенное
  • репликация данных на N нод, в том числе географическая
  • возможность чтения сразу с N нод
  • выход ноды из строя не должен влиять на производительность СХД
  • желателен posix интерфейс доступа к данным
  • ACL
  • поддержка лимитов дискового пространства на каждой из нод
  • быстро и надежно 🙂

Такие решения есть и Google Search пестрит названиями Ceph / glusterfs / parallel nfs / GFS / GPFS / Lustre и т д.

Но мы решили изучить решение предложенное нашими российскими разработчиками из Yandex Team в частности elliptics + pohmelfs.

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

К ребятам из Yandex есть огромная просьба привести в порядок документацию и добиться таки включения pomelfs в ядро. Еще смутило что на тот момент были известны только 2 продакшен инсталяции и одна из них была в Яндексе.

В elliptics нам сильно понравилось отсутствие серверов метаданных, key-value, а также «автоматический» ребалансинг нод. Хотелось найти ФС с похожими плюшками.

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

Завели. Из проблем могу сказать что в 3.3.1 не работал disk limit. Также оказалось что не получится сделать один большой том на все ДЦ так как слишком высокое latency (ping avg 100 max 700, jitter avg 30, stdev 40, loss 0.1% ) между ДЦ рождал существенное понижение производительности (со 150мбит до 40-50мбит).

В результате была выбрана следующая схема. Существует 2 тома: архивный и горячих данных. Том горячих данных свой в каждом ДЦ, и располагается на небольшом количестве нод с SSD накопителями. Архивный том находится в ДЦ с высоким SLA, но запущен на машинах с обычными SATA накопителями большого объема (1-3ТБ). Периодически запускается синхронизация данных, которая переносит трансляции запись которых уже была закончена в архивное хранилище.

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

Генерация и хранение метаданных трансляций
Метаданные — это информация о видеопотоке необходимая плееру для его воспроизведения, а также данные необходимые Recorder’ам для работы с трансляциями. Для хранения метаданных мы используем Redis. К нему пришли не сразу. Сначала очень привлек CouchBase, но потом он «отвлек» своим ценником в продакшен использовании. В Redis сохраняется информация о каждом чанке, при его поступлении.

Для генерации m3u8 плейлистов из метаданных также используется небольшой сервис, который при запросе клиента выбирает все необходимые данные из редиса и без труда генерирует плейлист. Масштабировать такой сервис горизонтально не составляет никакого труда. Отказоустойчивость Redis’a реализуется при помощи при помощи replication + redis sentinel.

Балансировка запросов на просмотр и кеширование популярного контента
Зачем это нужно ? В первую очередь для эффективной работы кеша на edge серверах. Зрителя нужно отправлять на тот эдж, где контент есть в кеше, и где этот же контент уже смотрят зрители, так как с большой вероятностью этот контент находится в cached памяти ОС. Таким образом мы снижаем количество random read чтений с диска, и увеличиваем эффективность работы edge сервера.

Для кеширования мы решили воспользоваться простым NGINX. Но столкнулись с одной проблемой, файлы в кеше nginx имеют в названии буквенно-цифровое название которое нам ни о чем не говорит. Чтобы узнать KEY (читай URL) которому принадлежит этот закешированный файл, нам нужно прочитать первые 4 строки файла, что в свою очередь относительно дорого с точки зрения файловых операций.

Путем некоторых доработок nginx, стало возможным получать информацию без таких существенных накладных расходов. Но получать информацию недостаточно, нужно агрегировать информацию по каждой единице контента, по количеству ее chunk’ов на данном сервере, а также учитывать то какие трансляции сейчас смотряться, нагрузку на диск / cpu / сетевые интерфейсы.

Для сбора всей этой статистики и мониторинга состояния кеша (добавление \ удаление контента) был написан отдельный c++ демон.

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

  • географическое положение пользователя
  • наличие запрошенного контента в кеше
  • просматривается ли этот контент где то на данный момент
  • системные параметры edge серверов (cpu / io / eth )
  • суммарная допустимая пропускная способность видеоплатформы.
  • необходимость редиректа запросов на данную единицу контента во внешнюю CDN
  • механизм broken edge (клиент не может подключиться к конкретному работающему серверу)

После чего клиенту выдается оптимальный для просмотра сервер.

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