Pull to refresh
VK
Building the Internet

Платформа для видеосервиса сроком в квартал

Reading time 14 min
Views 19K
Сегодня мы расскажем, как нам удалось построить свою платформу для сервиса видео на Одноклассниках на Java за 3 месяца.

Начнем с того, что представляет собой видеосервис на Одноклассниках. Он доступен как на вебе, так и в версиях для мобильных устройств. Одним из отличий Одноклассников от других соцсетей является наличие видеовитрины, где в разделах «ТОП недели», «Новинки» и каналах собраны видео. Для этих разделов видео отбирается автоматически по хитрому алгоритму на основании числа просмотров, классов и скорости роста популярности видео. И конечно, на витрине представлены каналы с контентом от партнеров:— сериалы, ТВ шоу, мультфильмы и кино.

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

Видеосервисом на Одноклассниках пользуется свыше 10 миллионов уникальных пользователей в день, которые смотрят свыше 70 миллионов видеороликов и загружает 50 тысяч видео в день.

Видеобаза Одноклассников насчитывает свыше 28 миллионов роликов. Исходящий трафик по вечерам достигает 80 гигабит в секунду. Ежедневно загружаемые 5 терабайт нового видео в день преобразуются в наш внутренний формат и на выходе получается 2 терабайта. Получившиеся файлы хранятся в трех копиях, что, в итоге, суммарно составляет 6 терабайт нового видео в день. Входящий трафик по загрузке достигает 2 гигабит в пиковые часы.



На чем это все работает? Сейчас парк видеосервиса насчитывает порядка 200 серверов, и скоро их будет значительно больше. Хранилище занимает 70 серверов, суммарный объем 5 Петабайт. 30 серверов занимаются хранением метаданных, это база данных и кэши перед ними. Еще 60 серверов занимаются трансформацией пользовательского видео в наши форматы. Еще 30 серверов — для загрузки и раздачи.



Видеосервис на Одноклассниках мы начали разрабатывать еще в 2010 году. Сначала у нас не было своей платформы и мы разрешали пользователям только делиться ссылками на контент, который располагался на других ресурсах: Rutube, YouTube, Видео Mail.Ru. В 2011 году мы запустили загрузку видео через портал Одноклассники, но все это работало на базе платформы Видео Mail.Ru. Сервис стал расти, стали предъявляться новые требования, и в январе 2013 года мы поняли, что без создания своей видеоплатформы наш сервис развиваться дальше не может.

Новая платформа — новые требования. Первое неотъемлемое требование, которое предъявляется ко всем серверам Одноклассников — это надежность, отказоустойчивость и полное сохранение функциональности в случае потери сервера или дата-центра. Далее идёт возобновляемая загрузка. Она необходима, иначе мы будем терять пользовательский контент. На третьем месте воспроизведение видео с любого момента без предварительной буферизации всего видео целиком. Все это нам хотелось видеть и на мобильных устройствах. Также мы очень любим и умеем эксплуатировать сервисы на Java, поэтому она также была добавлена к списку требований.



Посмотрим, как выглядит весь видеоконвейер изнутри. Пользователь загружает видео на upload-сервер (его мы будем обозначать на картинках стрелочкой вверх). Дальше видео попадает в серверное хранилище, а затем в сервер трансформации. На нём мы преобразуем видео в наш внутренний формат, сохраняем его во временное хранилище, после чего download-сервер (стрелочка вниз) раздаёт этот контент пользователям.



Рассмотрим подробнее реализацию каждого этапа.

Загрузка


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



Как выглядит наша система балансировки загрузки видео? У нас 6 серверов, по 2 в каждом датацентре. Для балансировки нагрузки внутри датацентров мы используем LVS (Linux Virtual Server). Это позволяет за одним IP-адресом хранить множество серверов и перебрасывать на них запросы, в зависимости от их загруженности.





Для обеспечения отказоустойчивости по датацентрам мы используем DNS-GSLB. Пользователь нажимает кнопку «Добавить видео», получает некий линк на загрузку, дальше доменное имя авторизовывается в IP-адрес одного из датацентров. DNS-GSLB — это Global Server Load Balancing, его основная функция заключается в обеспечении отказоустойчивости на уровне датацентров. В случае отказа датацентра наш DNS не будет разрешать (resolve, резолвить) доменное имя в этот датацентр.

Ещё раз: если отказал сервер, то пользователь перебрасывается на другой, а в случае отказа всего датацентра, новым запросом через DNS возобновляется загрузка через другой датацентр. Чем это удобно? Тем, что мы, во-первых, не пишем код, а используем готовые решения, а во-вторых, это позволяет сбалансировать нагрузку для любых statelessсервисов.



Теперь о том, как мы реализовали stateless-сервис. Пользователь начинает загрузку на конкретный сервер, а тот, в свою очередь, блоками порядка мегабайта отправляет данные в распределенное хранилище (о нем поговорим позже), обязательно по нескольким датацентрам. Upload-сервер реализован на Java, используется Apache Tomcat.



Что происходит, когда мы теряем сервер, датацентр, или, например, пользователь просто восстановил коннект через некоторое время после разрыва соединения? Наш новый upload-сервер, у которого нет никакой сессии с этим пользователем, по состоянию распределенного хранилища определяет последние успешно загруженные байты, и возобновляет загрузку с места разъединения. Отказоустойчивость практически стопроцентная. Даже если в процессе загрузки потерян датацентр, то процесс возобновляется с того места, где произошёл разрыв.



Трансформация (перекодировка)


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



При выборе формата мы проанализировали следующие данные: текущую распространенность браузеров и операционных систем, в том числе Android и iOS, возможность проигрывания в них видео через HTML5 и Flash. Так как текущий плеер Одноклассников реализован на Flash, мы приняли решение поддерживать как HTML5-видео, так и Flash.



Рассмотрев форматы, которые поддерживаются Flash и HTML5, мы выбрали МР4, в качестве медиаконтейнера — кодек Н264, аудиокодек — ААС.



Итак, после того, как видео успешно загружено, оно должно попасть на преобразование в наш внутренний формат. Это осуществляется на серверах трансформации, где мы используем нативную библиотеку FFMpeg. Серверов трансформации у нас сейчас порядка 60 штук. Что происходит с видео? Upload-сервер загружает видео во временное хранилище и в конце загрузки добавляет в очередь задачу на трансформацию.



Задания раздаются на серверы, но дело в том, что FFMpeg достаточно нестабильная штука, она иногда зависает, съедает все ресурсы системы, и бывают такие случаи, когда видео не конвертируется с первого раза. Поэтому мы добавили постоянное отслеживание статуса выполняемых задач. То есть процесс выглядит так: upload-сервер загрузил видео в очередь, сервер трансформации взял задачу и с некоторым интервалом проверяет статус её выполнения. В случае отказа сервера, задание перезапускается, либо возвращается обратно в очередь и передается на другой сервер.

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

Эта схема прекрасно работает, но у нас появляется Single Point of Failure, это наша очередь. В случае, если мы теряем датацентр, где у нас хранится очередь, или сам сервер, то у нас будут серьезные проблемы. Тут бы надо реализовать какое-то решение распределенной очереди, но мы не эксплуатировали такие вещи и не хотели усложнять инфраструктуру. Мы просто использовали Zookeeper, которым Одноклассники очень активно пользуются в других своих сервисах.

Что такое Zookeeper? Это сервер распределенных блокировок, на нем есть готовые решения в виде выбора лидера. Выбор лидера — это такой процесс в распределенной системе, когда в случае потери сервера вся система придет к стабильному состоянию благодаря назначению «лидера». Он знает о том, что он «лидер», и все серверы тоже это знают.



На самом деле, в этой системе «кандидат1» и «кандидат2» ничего не делают, это просто запасные серверы, расположенные в других датацентрах. Очередь полностью исполняется на «лидере». В случае потери «лидера» выбирается новый, и вся система узнаёт о том, что первый из кандидатов стал новым «лидером».



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

Для трансформации видео мы используем FFMpeg, а для извлечения всякой интересной информации используем МP4-парсер. Весь сервер трансформации написан на Java и работает достаточно просто — берет задачу из очереди, запускает FFMpeg через Java-обертку, нарезает видео в четыре качества, извлекает картинки для заставки и метаданные и, наконец, сохраняет всю полученную информацию в постоянное хранилище.

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



Хранение


После нарезки видео оно отправляется в хранилище. Сейчас оно состоит примерно из 70 серверов, на каждом из которых 36 двухтерабайтных дисков. Суммарно 5 петабайт. Хранилище распределенное, отказоустойчивое, оно называется One Blob Storage (OBS). Для хранения состояния кластеров мы используем Zookeeper. Видео хранятся на дисках блоками по 64 мегабайта. Для этого хранилища выход жесткого диска из строя — не проблема. На серверах имеются дополнительные диски. В случае выхода из строя одного из них, на другом автоматически восстанавливается его состояние. Каждое видео мы храним в трех экземплярах. Таким образом, из каждого видео получается четыре новых, каждое из которых сохраняется в трех экземплярах, по одному в каждом ДЦ.



Раздача контента


Наверное, самая интересная часть. Требования:
  • Отдача видео с любого момента без предварительной буферизации.
  • Высокая производительность, мы ожидали порядка 150 гигабит в секунду.
  • Отказоустойчивость.




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

Посмотрим, для начала, что такое псевдо-стримминг. При воспроизведении видео, в нем есть какая-то буферизированная часть. Если пользователь переходит в небуферизированную часть, то мы должны без предварительной буферизации начать воспроизведение с этого места. Как это устроено? Во время трансформации видео в наш внутренний формат, мы чередуем видео и аудио-сэмплы, а также каждые несколько секунд вставляем так называемый синк-сэмпл (sync sample). Получающийся в итоге результат выглядит следующим образом: у нас есть заголовок и есть блоки. Соответственно, когда пользователь выбирает какое-то место, то мы понимаем, с какого блока можно начать отдавать данные. В заголовке указаны начала каждого блока.



С одной стороны, выгодно использовать много таких блоков. Но тогда растет объем заголовка, и это тот объем, который пользователь вынужден скачать до появления первого кадра видео. Пытаясь оптимизировать заголовок, мы пришли к такому решению: максимальный объем заголовка видеофайла не должен превышать 6 Мегабайт. При этом в одном видео получается 500-700 блоков.

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

Псевдо-стримминг


Рассмотрим реализацию псевдо-стримминга в HTML5. Плеер имеет moov atom, при выборе какого-то места рассчитывает upset, сам запрашивает byte-range-request-данные и обеспечивает нативный поиск. У Flash-плеера ситуация несколько хуже, он просто делает при старте запрос на сервер (в URL есть время старта в секундах), а дальше сервер должен ему подготовить полноценный видеофайл начиная с этого места, то есть пересчитать заголовок, как будто бы видео запущено ровно с момента запроса пользователя.





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



Рассмотрим архитектуру нашего раздатчика. Это такая кэширующая прослойка между нашим постоянным хранилищем, она имеет несколько уровней кэшей. Исторически так сложилась, что нам на серверах выделяется порядка 96 Гигабайт RAM. Сюда мы ставим еще SSD-диски.



Алгоритм работы такой: пользователь запросил видео с сервера; если оно закэшировано в памяти, то мы отдаем его, если нет, то проверяем диск; если и на нем нет, то идем в наше хранилище. Видео из хранилища мы сразу помещаем в оперативную память и раздаем пользователям. В оперативной памяти мы используем только LRU-кэш и при вытеснении проверяем счетчик, сколько у нас просмотров этого сегмента. Если сегмент смотрели больше определенного количества раз, то мы вытесняем его на диск; если сегмент просматривался мало, то мы его выбрасываем.

Требования к кэшу объемом в 96 гигабайт:
  • Кэш не должен оказывать влияния на GC, он должен быть LRU. Сейчас у нас на сервере обрабатывается порядка 20 тысяч запросов в секунду, то есть он должен быть высокопроизводительным.
  • Кэш должен быть персистентным, то есть сохраняться после перезапуска или апдейта сервиса раздачи.


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



Большие объемы в 100 гигабайт и запрет на влияние на GC приводят нас к offheap-кэшированию. Вариантов offheap-кэширования, в принципе, много. Есть через Direct ByteBuffer, memory-mapped файлы, но у них есть проблемы. Например, нативный код не переносим, а в этих решениях ограничение максимального объема выделяемых файлов порядка 2 гигабайт. Конечно, можно их собрать из блоков, но это будет не очень удобно. Вариант решения: использовать shared-memory, для обращения к ней использовать Unsafe. Все это реализовано на нашей open source-библиотеке one-nio, ее можно посмотреть на github.



Для раздачи мы используем селекторы. Наш раздающий HTTP-сервер — это часть библиотеки one-nio. Сервер оборудован двумя гигабитными оптоволоконными картами. Для доступа к хранилищу у него имеется 10 гигабитных Ethernet-карт.



По результатам тестирования мы успешно справляемся с раздачей 20 Гигабит. То есть столько, сколько нам могут обеспечить внешние сетевые карты. При этом нагрузка на процессор порядка 30%. Сейчас наши инженеры работают над тем, чтобы переконфигурировать систему и начать раздавать больше. Основное ограничение — 6 слотов на одном сервере и эффективность нашего кэша. Она у нас получилась где-то 80 /20: 80% запросов блоков берется из кеша, 20% запросов достаются из OBS. Наверное, это неплохо, но для 20 Гигабит мы почти полностью выбираем ресурс 4 гигабит Ethernet-доступа к нашему постоянному хранилищу.

Балансировка


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



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



Если Сервер упал, соответственно, раздача происходит этой партицией с одного сервера, а другой партицией — с другого. Нагрузка равномерно распределяется по всем серверам, и все бы здорово, но у этих серверов контент не закэширован. Таким образом, мы образуем большое количество запросов в наше постоянное хранилище. Оно может такого и не выдержать, особенно, в часы пик. Для этого мы «прогреваем» партиции на будущее, то есть иногда отправляем пользователя с некоторой вероятностью на реплики — на реплику1 или реплику2. Таким образом, мы убиваем сразу двух зайцев: прогреваем этот кэш и балансируем нагрузку на хранилище, а с другой стороны, эффективно используем кэш внутри кластера.

Также не забывайте, что на этом HTTP-сервере byte range реализуется легко, а вот реализовать перестроение moov atomс какого-то места не самая простая задача. Само перестроение происходит на раздающих серверах. Для этого мы попробовали воспользоваться MP4-парсером, но он генерировал очень много мусора, оказывал большое влияние на GC и был крайне неэффективен. У нас количество запросов на перемотку — сотня в секунду. Мы отказались от МР4-парсера и реализовали свое решение на Java для перестроения moov атома с какого-то времени. Оно оказалось крайне эффективным, заголовок перестраивается из 6 мегабайт в 3 за время до десяти миллисекунд.

Есть такой интересный факт: в августе 2012 года, когда марсоход совершил посадку на Марс, НАСА решило организовать онлайн-трансляцию видео. Для этого они пригласили nginx. У nginx была платформа, раздающая, кэширующая, развернутая на 40 Amazon-ЕС2-серверах. Это, конечно, не суперэффективные серверы, но 40 штук — достаточно серьезно. Была приглашена тестирующая компания, которая проверяла нагрузку на этот видеосервис, и тестировали они нагрузку порядка 25 гигабит в секунду. Я думаю, что один наш сервер после некоторых модификаций будет выдерживать 25 гигабит в секунду. Они проводили отключение серверов. Сначала вывели 10 серверов, трафик немножко просел, перебалансировался и вернулся к норме. Они отключили еще 10 серверов, трафик сел и уже не вернулся к 25 гигабитам, а остался на уровне 12 гигабит в секунду. Получилось, что раздача на nginx, это, конечно, была раздача потокового видео, но у них был всего один видеострип. 20 серверами была обеспечена производительность порядка 12 гигабит.



Ниже представлены основные вехи нашего проекта. Стоит обратить внимание, что мы начали разработку в январе 2013 года, а в мае 2013 года вся платформа на Java была запущена на 100% порталов.



Технологии


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

С другой стороны, технологии мы использовали крайне мало, это наша внутренняя библиотека One-nio, которая у нас доступна в open source. Из всего этого проприетарное только наше хранилище, OBS, но это связано с некоторыми проблемами, необходимостью отвязать его от нашей внутренней архитектуры конфигурации. Единственным альтернативным решением стал FFMpeg, который использовался для нарезки видео.



Собственно, вот как все эти технологии распределены по нашим серверам. Все, кроме FFMpeg, это Java.



Результаты


Мы обеспечили полную работоспособность сервиса в случае потери датацентров и серверов. Мы дали пользователям такую возможность, как возобновление загрузки видео в течение 12 часов после разрыва соединения. То есть, если вы загружали видео, и у вас пропала сеть (это особенно актуально для мобильных устройств), если вы в течение 12 часов продолжите загрузку, то мы полноценно догрузим файл.
Также мы обеспечили просмотр видео с любого момента времени во Flash и HTML5. Последний очень важен для мобильных устройств. Добились интересных результатов в плане трафика с одного сервера.



Основные планы на ближайшее будущее — попробовать дополнения на HTML5-плеер. Также можно улучшать работы FFMpeg с кодеками. Мы получаем много всяких интересных метаданных из загружаемого пользователями видео, типа GPS, но GPS-координаты мы пока никак не используем.



И еще несколько слов о видеоплатформе


Мы позволяем пользователям Одноклассников загружать к нам видео размером до 8 гигабайт, что в четыре раза превышает максимально допустимый для загрузки размер видео во ВКонтакте. И длительность загружаемого видео ничем не ограничена (у YouTube, например, для некоторых аккаунтов есть ограничение в 15 минут). Предельное разрешение видео, однако, меньше, чем хотелось бы — всего 720р, что, впрочем, соответствует стандарту HD. Тем не менее, скоро мы обзаведемся новыми мощностями, что позволит поднять качество до желаемых 1080р.

Кроме того, в отличие от других видеоресурсов, сервис Одноклассники поддерживает расширенный спектр видеоформатов — их больше 20, в числе которых .evo и .asf. Это наибольший показатель среди конкурентов, хоть они и поддерживают все основные форматы, включая MP4.

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

Мощный бекенд в лице разработанной нами видео-платформы позволил воплотить на Одноклассниках полноценный онлайн-кинотеатр. Лицензионный контент, который мы получаем от наших партнеров, доступен для пользователей социальной сети совершенно бесплатно. У нас можно найти как популярные российские сериалы, например, «Кухня», которая появляется в Одноклассниках сразу после показа на ТВ, так и зарубежные — «Плохие», «Шерлок» и другие. А как известно, найти лицензионный контент в других социальных сетях — задачка не из простых.
Tags:
Hubs:
+28
Comments 9
Comments Comments 9

Articles

Information

Website
vk.com
Registered
Founded
Employees
5,001–10,000 employees
Location
Россия
Representative
Миша Берггрен