Хабрахабр

[Перевод] Каскадные SFU: улучшаем масштабируемость и качество медиа в WebRTC-приложениях

В развертывании медиасерверов для WebRTC есть две сложности: масштабирование, т.е. выход за рамки использования одного сервера и оптимизация задержек для всех пользователей конференции. В то время как простой шардинг в духе «отправить всех юзеров конференции X на сервер Y» легко масштабируется горизонтально, он все же далеко не оптимален в плане задержек. Распределять конференцию по серверам, которые не только близко расположены к пользователям, но и взаимосвязаны – звучит как решение для обеих проблем. Сегодня мы подготовили перевод подробного материала от Бориса Грозева из Jitsi: проблемы каскадных SFU, с описанием подхода и некоторых трудностей, а также подробности внедрения. Стоит сказать, что конференции Voximplant тоже используют SFU; сейчас мы работаем над каскадированием SFU, которое должно появиться в нашей платформе в следующем году.

Мышиные нейроны. Изображение NIHD (CC-BY-2.0)
Коммуникации в реальном времени очень чувствительны к сети: пропускная способность, задержки и потери пакетов. Снижение битрейта ведет к снижению качества видео, длительная сетевая задержка ведет к длительной задержке у конечных пользователей. Потеря пакетов может привести к «дерганому» аудио и фризам на видео (из-за пропуска кадров).

Когда есть только два пользователя, то это просто – WebRTC использует протокол ICE чтобы установить соединение между участниками. Поэтому для конференции очень важно выбрать оптимальный маршрут между конечными устройствами/пользователями. WebRTC умеет резолвить доменное имя, чтобы получать адрес TURN-сервера, благодаря чему можно легко выбирать локальный TURN на основе DNS, например, используя свойства AWS Route53. Если возможно, то участники соединяются напрямую, в ином случае используется TURN-сервер.

Многие WebRTC-сервисы используют Selective Forwarding Units (SFU), чтобы более эффективно передавать аудио и видео между 3 и более участниками. Тем не менее, когда роутинг множества участников происходит через один центральный медиасервер, ситуация становится сложной.

Проблема со звездой

В топологии «звезда» все участники соединяются с одним сервером, через которые они обмениваются медиа потоками. Очевидно, что выбор расположения сервера имеет огромное значение: если все участники расположены в США, использовать сервер в Сиднее – это не лучшая идея.

Многие сервисы используют простой подход, который неплохо работает в большинстве случаев: они выбирают сервер поближе к первому участнику конференции. Однако бывают случаи, когда это решение неоптимально. Представим, что у нас есть три участника с картинки выше. Если австралиец (Caller C) первым подключится к конференции, то алгоритм выберет сервер в Австралии, однако Server 1 в США будет лучшим выбором, т.к. он ближе к большинству участников.

Если считать, что пользователя подключаются в случайном порядке, то описанная ситуация происходит с ⅓ всех конференций с 3 участниками, один из которых сильно удален. Описанный сценарий – не очень частый, но имеет место.

В этом случае порядок подключения неважен, у нас всегда будет группа близко расположенных участников, которые вынуждены обмениваться медиа с удаленным сервером. Другой и более частый сценарий: у нас есть две группы участников в разных локациях. Например, 2 участника из Австралии (C&D) и 2 из США (A&B).

Переключиться на Server 1 будет неоптимально для участников C&D. Server 2 неоптимален для A&B. То есть какой бы сервер ни использовался, всегда будут участники, подключенные к удаленному (= неоптимальному) серверу.

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

Решение: каскадирование

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

SFU-соединение между C и D не изменилось – по-прежнему используется Server 2. Для участников A и B используется Server 1, и это очевидно лучше. Самое интересное – связь между, например, A и C: вместо AServer 2C используется маршрут AServer 1Server 2C.

Неявное влияние на скорость обмена

В соединении SFU есть свои плюсы и минусы. С одной стороны, в описанной ситуации время обмена между участниками становится больше при добавлении новых прыжков по сети. С другой стороны, имеет место уменьшение этого времени, когда мы говорим про связь «клиент» – «первый сервер», потому что мы можем восстанавливать медиапоток с меньшей задержкой по принципу hop-by-hop.

WebRTC использует RTP (обычно поверх UDP), чтобы передавать медиа. Как это работает? Когда теряется UDP-пакет, то можно игнорировать потерю или запросить повторную отправку (ретрансмиссию), используя пакет RTCP NACK – выбор уже на совести приложения. Это означает, что транспорт ненадежен. Например, приложение может проигнорировать потерю аудиопакетов и запросить ретрансмиссию некоторых (но не всех) видеопакетов, в зависимости от того, нужны ли они для декодирования последующих кадров или нет.

Ретрансмиссия RTP-пакета, один сервер

К примеру, в маршруте A-S1-S2-C, если пакет потерян между A и S1, то S1 это заметит и запросит ретрансмиссию; аналогично с потерей между S2 и C. Когда есть каскадирование, ретрансмиссия может быть ограничена локальным сервером, то есть выполняться на каждом отдельном участке. И даже если пакет потерян между серверами, принимающая сторона также может запросить ретрансмиссию.

Ретрансмиссия RTP-пакета, два сервера. Обратите внимание, что Server 2 не запрашивает пакет 2, потому что NACK пришел вскоре после отправки пакета.

Размер буфера динамически меняется в зависимости от времени обмена между сторонами. На клиенте используется джиттер буфер, чтобы задержать воспроизведение видео и успеть получить отложенные/ретрансмитные пакеты. Когда происходят hop-by-hop ретрансмиссии, задержка уменьшается, и как следствие, буфер может быть меньше – в итоге общая задержка тоже уменьшается.

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

Внедряем каскадные SFU: кейс Jitsi Meet

Сигнализация vs. Медиа

Давайте взглянем на сигнализацию. С самого начала Jitsi Meet разделил концепцию сервера сигнализации (Jicofo) и медиасервера/SFU. Это позволило внедрить поддержку каскадирования относительно просто. Во-первых, мы могли обрабатывать всю логику сигнализации в одном месте; во-вторых, у нас уже был протокол сигнализации между Jicofo и медиасервером. Нам нужно было только немного расширить функциональность: у нас уже поддерживались множественные SFU, подключенные к одному серверу сигнализации, надо было добавить возможность одному SFU подключаться ко множеству серверов сигнализации.

схему: В итоге появилось два независимых пула серверов: один для инстансов jicofo, другой для инстансов медиасервера, см.

Пример организации серверов на AWS с возможностью каскада между разными дата-центрами.

Мы хотели сделать эту часть максимально простой, поэтому между мостами нет сложной сигнализации. Вторая часть системы – связь bridge-to-bridge. Вся сигнализация идет между jicofo и jitsi-videobridge; соединение между мостами используется только для аудио/видео и сообщений канала передачи данных.

Протокол Octo

Чтобы управлять этим взаимодействием, мы взяли протокол Octo, который оборачивает RTP-пакеты в простые заголовки фиксированной длины, а также позволяет передать текстовые сообщение. В текущей реализации, мосты связаны по полносвязной топологии (full mesh), однако возможны и другие топологи. Например, использовать центральный сервер (звезда для мостов) или древовидную структуру для каждого моста.

Будущие версии Octo могу использовать этот подход. Пояснение: вместо оборачивания в Octo-заголовок можно использовать расширение RTP-заголовков, которое сделает потоки между мостами на чистом (S)RTP.

Вначале мы хотели использовать центральный сервер, и это напомнило нам осьминога. Второе пояснение: Octo не означает ничего. Так появилось имя для проекта.

Формат Octo-заголовка

Этот канал отвечает за отправку/получение медиа в/из других мостов. В терминологии Jitsi, когда мост – это часть конференции с множественными мостами, то у него есть дополнительный Octo-канал (на самом деле, один канал на аудио и один на видео). Каждому мосту назначается свободный порт для Octo (4096 по умолчанию), поэтому нам нужно поле Conference ID, чтобы обрабатывать множественные конференции.

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

Simulcast

Simulcast позволяет каждому участнику отправлять несколько медиапотоков с разными битрейтами, в то время как мост помогает определить, какие из них нужны. Чтобы это правильно работало, мы передаем все simulcast-потоки между мостами. Благодаря этому можно быстро переключаться между потоками, потому что локальный мост не должен запрашивать новый поток. Однако это не оптимально с точки зрения bridge-to-bridge трафика, т.к. некоторые потоки редко используются и лишь нагружают полосу пропускания без всякой цели.

Выбор активного участника

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

Выбор моста

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

в документации. Подробности про Octo см.

Разворачиваем каскадные SFU

Для деплоя мы использовали машины в Amazon AWS. У нас были серверы (сигнализации и медиа) в 6 регионах:

  • us-east-1 (Северная Вирджиния);
  • us-west-2 (Орегон);
  • eu-west-1 (Ирландия);
  • eu-central-1 (Франкфурт);
  • ap-se-1 (Сингапур);
  • ap-se-2 (Сидней).

Мы использовали инстансы HAProxy с геопривязкой, чтобы определять регион участника. Домен meet.jit.si управляется Route53 и резолвится в инстанс HAProxy, который добавляет регион в HTTP-заголовки отправляемого запроса. Заголовок позже используется в качестве значения переменной config.deploymentInfo.userRegion, которая доступна на клиенте благодаря файлу /config.js.

При наведении курсора на верхний левый угол локального видео покажет общее количество серверов и сервер, к которому подключены вы. Интерфейс jitsi показывает, сколько мостов используется и к каким привязаны конкретные пользователи – в целях диагностики и демонстрации. Также вы увидите время обмена между вашим браузером и браузером собеседника (параметр E2E RTT). Аналогично можно увидеть и параметры второго участника.

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

Заключение

Изначально Octo появился в качестве A/B теста. Первые результаты были хороши, поэтому сейчас Octo доступен всем. Предстоит пропустить еще много трафика через него и подробнее изучить производительность; также планируется использовать эти наработки для поддержки еще более крупных конференций (когда одного SFU уже недостаточно).

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть