Персональные инструменты
 

Разработка видеохостинга на Erlang (Максим Лапшин на ADD-2010)/Стенограмма

Материал из CustisWiki

Перейти к: навигация, поиск

Что такое стриминг?

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

Итак, что такое стриминг вообще.

Ютуб — это не стриминг

Что такое YouTube? Там храниться огромное количество разного видео, но это не стриминговый проект, там нет стриминга. Там просто десятисекундные ролики, которые выдаются вам nginxoм, ну или другим вебсервером.

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



Пользовательское ТВ

Пользователь загружает видеофайлы

  • Составляет плейлист
  • По запросу других плейлист начинает проигрываться
  • Если никому не нужно, то видео не играется

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

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


Почему же не сделать это все на обычном nginxe?

Вроде как обкатанная технология, у того же ютуба работает, но нет, не работает.

Что делает стример?

Что делает стример?

  • Распаковывает видео и аудио из файловых контейнеров
  • Упаковывает в транспортный контейнер
  • Посылает кадры синхронно с реальным временем

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

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

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

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

Отступление про кодеки

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

Контейнер — это то, во что упаковываются уже закодированные данные. Например, если вы встретили h264 или AAC, то это видео и аудиокодеки, соответсвенно. А MP4 … — нет такого кодека, это контейнер, в который можно убрать абсолютно любое видео и абсолютно любой звук.

  • Кодек — формат представления сжатых аудио и видео данных
  • Контейнер — формат упаковки одного и более потоков аудио и видео в файле или в потоке
  • H.264/AAC — лучшие кодеки
  • MP4 — самый компактный файловый контейнер

Этапы User TV

Этапы User TV

  • Скачать плейлист
  • Распаковать файл
  • Упаковать кадры в транспортный контейнер (RTMP, MPEG-TS,…)
  • Зачистить всe, когда уйдут клиенты
  • Позволить обновить код, не отключая клиентов

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

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

Традиционные способы решения

На чем это обычно люди делают, такие решения? Традиционные способы решения, это традиционные инструменты — Java, С++, на них есть продукты, которые занимаются потоковым видео. Это например, Red5, бесплатный, платная Wowza, написанная на Java, либо это rtmpd, написанный на C++.


Парсинг mp3 на Java

В чем проблема…? Ну вот пример я привел, это маленький кусочек кода, как парсится RTMP на Java, это маленький кусочек кода, одна сотая файла.

Вот так выглят любой сервер на Java, можно присматриваться — это малая, малая, часть файла. Вникнуть в это очень сложно.

if (id3v1 instanceof ID3V1_1Tag) {
   try {
     // Add the track property
     graph.add(mp3Resource, processor.resolveIdentifier(IdentifierProcessor.TRCK),
         factory.createLiteral("" + ((ID3V1_1Tag) id3v1).getAlbumTrack()));
   } catch (GraphException graphException) {
     throw new ParserException(
         "Unable to add track number to id3v1 resource.",
         graphException);
   } catch (GraphElementFactoryException graphElementFactoryException) {
     throw new ParserException(
         .... ещѐ 600 строк кода
         graphElementFactoryException);
   }
 }


Парсинг mp3 на Erlang

Это все, что нужно написать на Erlange, чтобы декодировать MP3. Все. Пять строчек. Оно уже все распаковано и можно отправлять пользователям.


decode(<<2#11111111111:11, VsnBits:2, LayerBits:2, _:1, BitRate:4, _/binary>> = Packet) ->
  Layer = layer(LayerBits),
  Version = version(VsnBits),
  <<Frame:(framelength(bitrate({Version,Layer}, BitRate))/binary, Rest/binary>> = Packet,
  {ok, Frame, Rest}.


Соответственно, что мы получаем — уже начиная с самого начала, с распаковки файла, что-то не то, и в Java, и в C++, очень много кода, очень много накладной логики мы пишем в нашем коде.

Но все становится…, весь этот синтаксический сахар, все это становится, совершенно неважным, когда к нам приходят тысячи клиентов.

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

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

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

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

Проблемы классических решений при тысячах клиентов

  • Управление памятью: утекание, либо преждевременное высвобождение
  • Контроль за ресурсами клиентов
  • Хаотическое разрушение системы при сбое в одном месте
  • Ввод/вывод при обслуживании тысяч клиентов

Что же получается? Сервер Red5 валится под сотней пользователей. Эх, тут к сожалению, не красная получилось (см. картинку с унылым запорожцем…)

Разработка видеохостинга на Erlang (Максим Лапшин на ADD-2010).pdf

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


Разработка видеохостинга на Erlang (Максим Лапшин на ADD-2010).pdf

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

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

Его данные остались навсегда, мы не можем получить информацию, что надо отключить. И все, до рестарта сервера.


epoll/kqueue сложны для долгих соединений из-за управления памятью.

Что же касается ввода-вывода, то механизмы epoll/kqueue, для которых есть библиотеки libevent, это единственный способ обслуживать тысячи сокетов, они очень, очень сложны, для … когда у вас начинается сложная бизнес-логика…, потому что эффективное управление памятью в eventной модели, по-моему, безумно сложно.


Разработка видеохостинга на Erlang (Максим Лапшин на ADD-2010).pdf

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


Корни проблем

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

Разработка видеохостинга на Erlang (Максим Лапшин на ADD-2010).pdf

Вот, к сожалению, картинка опять не видна.

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

Разработка видеохостинга на Erlang (Максим Лапшин на ADD-2010).pdf


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

Web-подход → «пускай течет, скоро прибьем» → не работает.

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

Erlang решает эти проблемы радикально

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

Это было сделано на 90 % из-за его концепции процессов.

Процессы

  • Параллельные потоки выполнения
  • Изолированная область памяти
  • Обмен через посылку сообщений
  • Переменные неизменяемые
  • Нет данных вне процессов

Процессы в Erlange, это что-то типа тредов, в обычных системах. Они легковесны, они гораздо меньше места занимают, и самое важное — они абсолютно изолированы.

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

Question.svg А ведь бинари по ссылке передается между процессами?

Ну это тонкости реализации, а на самом деле эти бинари можем отследить всегда. Они исследуются.

Question.svg А чем и как?

Мы знаем, процессу известно, на какие бинари и какого размера он ссылается.

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

Все данные хранятся внутри перечислимых объектов

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

Обработка ошибок

  • Их можно ловить
  • Если не ловить, то завершается процесс
  • Соседи об этом узнают через сообщения
  • Гарантированная зачистка ресурсов


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

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

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

Слежение за процессами

  • Связи
  • Супервизоры
  • appmon

К сожалению, я не смогу показать на своем ноутбуке, у меня нет переходника, но я хотел бы показть такую вещь, как app monitor. Это поставляющися, опять таки с платформой механизм, который позволяет вам графически, посмотреть список всех процессов, которые есть у вас, с их деревом. То есть мы можем… , это очень полезная вещь, когда вы можете видеть, что к вам приходит пользователь, у вас создался объект под него, он запросил какие-то ресурсы, под них просто залез (???) в какие-то процессы… Пользователь уходит, процессы остаются — фактически это утечка процессов, а с помощью app monitora, все это видится очень наглядно.

Но, к сожалению, не покажу вам. :(

В Erlang настоящее горячее обновление кода

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

Без отключения клиентов!

Других систем, которые позволяют такое делать я не знаю.

Какие результаты использования Erlang?

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

Erlyvideo:

  • Мультипротокольный сервер
  • Держит тысячи клиентов на одном сервере
  • Существующая инфраструктура для плагинов

Например, совершенно нормально обслуживает тысячи клиентов с одного сервера, сейчас стоит в продакшне у BD (???) и работает.

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

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

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


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

Выводы

Задачи потокового видео имеют специфику, отличающую их от веба:

  • Необходимы инструменты эффективные и высокоуровневые одновременно
  • Erlang прекрасно вписывается в эту нишу
  • Практическое использование показало эффективность выбора

В итоге, какие же выводы можно сделать из применения Erlanga для этой задачи?

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

У вас не будет этих проблем, потому что… просто в силу специфики органиазции кода.

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

Применимость erlang

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

  • Видеостриминг (erlyvideo)
  • Jabber-сервер (ejabberd)
  • Банковский процессинг (Приват Банк)
  • Онлайн игры (Online Poker)

Однако, у него есть другие ниши применимости, например, на том же эрланге сделан самый лучший сервер Jabbera, это ejabberd, он настолько крут, что вот в яндексе, например, несмотря на жуткую антипатию к эрлангу, решили применять его, да, Яш? Сильно Гриша не любит Эрланг (bobuk)? Он очень ругался, плевался, но выбора у них не осталось — это говорит очень многое о продукте. Также, например, доистоверно известно, банки делают системы банковского процессинга. Я не знаю, конечно, какие именно детали, детали конечно не раскрываются, но я знаю, что у того же Приват-банка, есть еще ряд компаний, переводящих свои системы долгоживущего процессинга на эрланг, потому что им оказывается это удобным.

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

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

И вот даже есть онлайн-реализация покера на эрланге.


Вопросы?

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


Question.svg Ну вот, хотел бы даже не некоторый вопрос, а скорее добавление. Во-первых, по горячему обновлению кода — оно реализовано, в nginxe, том же, вот, но без ерланга. И вот тот же ejabber, который очень плохо обновляется, на лету его обновить — это проблема.

В nginxe, горячего обновления кода — именно кода, не существует. Там есть горячее обновление бинарников. У вас стартует новая версия кода, биндится на старые сокет, при этом, сохраняя в памяти старый, если что-то пошло не так, и оно начинает обслуживать новых клиентов. И очень важно — что под теми клиентами, которые оно обслуживает прямо сейчас, вы не можете менять код. Ну потому, что написан на Си, и нет механизмов подмены байт-кода… на таком уровне. Поэтому то что сделано в nginxe, в nginxe сделано очень хорошее решение, прекрасно годится для сотен тысяч запросов в секунду, когда у нас их очень много, и ничего страшного — когда старые запросы работают со старым сервером, а новые уже будут новым сервером обрабатыватся.

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

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

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


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

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

Какие еще вопросы есть?


Question.svg По поводу утечек — что мешает эрланговскому процессу висеть и продолжать жрать память?