DevConf 2018 состоится 18 мая в Москве, в Digital October. А мы продолжаем публиковать доклады с прошлогодней конференции. На очереди доклад Алексея Акуловича из ВКонтакте, где он поведает о том, о чем внимательные читатели уже догадались по сабжу.


В 2015 году мы использовали стороннее решение. Мы встраивали(embed) его плеер на сайт как youtube и оно работало. Работало не идеально, но на тот момент оно нас устраивало по объему трансляций, который через него можно было пустить, а также по качеству и задержкам, которые оно давало. Но мы из него довольно быстро выросли.

Первая причина — задержки. Когда зрители пишут в чате вопросы, а задержка трансляции 40 секунд, то в большинстве случаев это неприемлемо. Основная задержка происходила в передаче сигнала между компьютерами стримера и зрителем(полезный хабрапост на эту тему). Основные протоколы передачи стримингового видео: RTMP, RTSP, HLS, DASH. Первые два — это протоколы без повторных запросов, т.е. мы подключаемся к серверу и он просто вливает данные стрима. Задержка минимальная, может быть меньше секунды, т.е. с этим все хорошо.

HLS и DASH — это протоколы, основанные на HTTP, делающие новый запрос за каждым фрагментом данных. И это создает проблемы. Когда мы начали воспроизводить первый фрагмент, нам надо сразу запросить второй, чтобы когда закончили с первым, второй должен быть скачан и распарсен. Это нужно, чтобы обеспечить непрерывное воспроизведение. Таким образом, минимальная задержка по этим протоколам — два фрагмента. Один фрагмент — это примерно несколько секунд. Поэтому, добиться приемлемой задержки с этими форматами не получится.



У нас есть два варианта использования стрима. Первый — через наши приложения, когда у нас полный контроль над разрешением, кодеками и т.д. И мы можем этот сигнал отдавать зрителям без обработки. Второй вариант, когда мы даем возможность стримить с чего угодно, каким угодно софтом. Разумеется, у нас в этом случае нет никакого контроля над кодеками, разрешением, качеством. Мы принимаем этот сигнал на вход и должны выдать его зрителям в хорошем качестве. Например, если один игрок стримит игру в 4К качестве, а зритель пытается смотреть по телефону в 3G и ничего не видит, то виноваты будем мы. Поэтому сторонний сигнал должен быть обработан нами и отдан зрителю в нужном разрешении.

Опираясь на вышеперечисленное, мы пришли к таким протоколам:

  • Для передачи без обработки: RTMP, чтобы обеспечить передачу без задержки
  • С обработкой / fallback: HLS, поскольку там и так уже запланированы задержки на обработку/транскодинг.

На тот момент нам были известны такие решения.

Red5


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

Erlyvideo


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

Wowza


Wowza на тот момент использовалась в дружественном проекте и была возможность спросить. И мы взяли ее на тесты.

Плюсы: она действительно много чего умеет.

Минусы:

  • «Форум-ориентированное» конфигурирование. Она имеет документацию, но чтобы что-то найти, надо гуглить. А в гугле все ссылки ведут на форумы. И все решения, которые мы нашли, мы нашли на форумах.
  • XML везде. Даже приходилось задавать куски xml в браузерном интерфейсе.
  • Для того, чтобы получать callback на такие простые вещи как “пользователь начал трансляцию”, “закончил”, “проверка авторизации” нужно писать модуль для wowza на java.

Совсем минусы:

  • На тестовую машину(16Gb RAM + 4Gb swap) пустили 4-5 трансляций и сами(без какого-либо пользовательского трафика) смотрели некоторые и wowza заняла всю память и машина засвопилась. Приходилось перезапускать ее каждый день.
  • Иногда wowza “била” стримы, когда стримеры реконнектились. Т.е. она писала запись на диск, но потом сама же не могла ее воспроизвести. Мы писали в саппорт, но они никак не помогли. Пожалуй это стало причиной отказа от wowza. Поскольку остальное можно было пережить.

Nimble Streamer


Еще одна отечественная разработка. Была гораздо более дружественно настроена. Все нужные нам фичи или были готовы, или были запланированы на ближайшее время. Написана на С++ и теоретически могла запуститься даже на Raspberry Pi, т.е. в плане потребления была на голову выше java. Также у них была библиотека для реализации плеера на мобильных устройствах(iphone, android).

Как все выглядит?

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



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



Итак, плюсы:

  • Классная панель.
  • Потребление ресурсов несопоставимо лучше, чем wowza.
  • Удобное, хорошее API.
  • Суммарно стоит дешевле чем wowza.

Минусы:

Когда мы стримим в HLS, плеер обращается по HTTP к файлу-манифесту со списком качеств. Особенность в том, что Nimble формирует этот файл на лету, на основе того, какой ему пришел запрос. Если пришли по https, то он отдает https-ссылки. Если по http, то http. У нас он стоял не напрямую для зрителей, а за nginx и проблема в том, что зритель приходит по https, а он отдает http-ссылку и плеер не может воспроизвести. Единственное решение — саб-фильтр на уровне nginx, который изменяет адреса ссылок. Костыль.

Второй минус в том, что для управления через API запросы отправляется не на сам сервис, а на стороннему сервис Nimble. При этом доступ к нему идет по whitelist IP без подсетей, а мы хотели туда ходить с подсети примерно в 128 IP. Форма в панели позволяет вводить по одному IP. Пришлось делать прокси для этого API. Не бог весть какая проблема, но она есть.

Есть еще проблема с асинхронным API Nimble. Сервер-бинарник синхронизируется с API по расписанию. Т.е. если мы добавляем нового стримера, он создает стрим, настройки по качеству, но обновятся они только через 30 секунд допустим.

Текущая архитектура




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



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



Тем самым, мы закрыли от стримеров всю нашу внутренную кухню. Трафик идет на nginx, а дальше он сам проксирует его куда надо. На уровне самого модуля есть возможность переписать(rewrite) rtmp-ссылку и перенаправить поток туда. Это пример из документации(без форумов и xml!):



Он идет по адресу on_publish, тот переписывает адрес на новый и стрим идет по этому адресу. Несколько входящих серверов сидят на одном IP и на уровне балансировщика трафик распределяется по ним.



С раздачей тоже самое. Мы хотели спрятать от зрителей внутренности. Чтобы зрители не ходили напрямую на машину, которая обрабатывает видео. По аналогии со входящими, у нас есть раздающие сервера. Там тоже используется nginx. Для RTMP используется тот же самый rtmp-модуль. А для HLS используется proxy_cache с tmpfs для хранения фрагментов m3u8 и HLS.
Летом 2016 года к нам пришел International(турнир по Dota 2). И мы поняли, что наша схема… плохая :)



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



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



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



Для возможности перенаправлять юзера мы сделали так называемый rtmp-редирект. Ссылка ведет на https. Если машина не нагружена, то она отдаст редирект на rtmp. Иначе, на другой https. И плеер знает когда он может играть, а когда должен редиректиться.



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



При этом не все рабочие машину у нас на Nimble. Там где нам не нужно транскодирование мы используем тот же nginx с rtmp-модулем.

Что дальше?


На данный момент у нас каждый день начинается примерно 200 тысяч стримов(в пике 480 тысяч). Около 9-14 миллионов зрителей каждый день(в пике 22 миллиона). Каждый стрим записывается, транскодируется и доступен как видео.

В ближайшем будущем(которое наверняка уже наступило, доклад то прошлогодний) планируется расшириться до миллиона зрителей, 3 Тб/сек. Полностью перейти на SSD, поскольку рабочие машины очень быстро упираются в диск. Вероятно, заменим Nimble и nginx на свой велосипед, поскольку там есть еще минусы, о которых я не упомянул.



Чем присутствие на конференции от просмотра/чтения доклада отличается? Тем, что на конференции можно подойти к Алексею(и не только к нему!) и узнать конкретные детали, которые вас интересуют. Пообщаться, обменяться опытом. Как правило, доклады лишь задают направление для интересных разговоров.

Приходите послушать доклады и пообщаться. Читателям Хабра регистрация со скидкой.

Комментарии (0)