Как стать автором
Обновить
VK
Технологии, которые объединяют

Как это устроено: видеоконференции ВКонтакте на безлимитное число участников

Время на прочтение20 мин
Количество просмотров20K

Сервисы для онлайн-общения и всевозможная доставка — наверное, самые востребованные и активно развивающиеся отрасли 2020–21-го. Мы ВКонтакте тоже не остались в стороне: работая удалённо с первых месяцев пандемии, запустили групповые видеозвонки. Сперва они вмещали одновременно 128 человек, а теперь мы полностью сняли лимиты на число участников.

В этой статье рассказываем, с какими трудностями сталкивается большинство сервисов звонков. И показываем, что нам понадобилось сделать и изобрести, чтобы преодолеть ограничения по числу участников. Попутно отвечаем на вопросы, которые прилетали со всех сторон на волне интереса к технологиям real-time коммуникации: как устроены Zoom и Clubhouse, что взять для своего сервиса звонков из open source, как встроить звонки в приложение. Про эффективную доставку тоже будет — но не еды, а данных, аудио и видео.

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

Что происходило с сервисами видеоконференций во время пандемии?

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

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

  • Март-2020. Discord увеличил число участников видеочата до 50.

  • Апрель-2020. WhatsApp тоже покрутил константу и увеличил количество участников групповых звонков с 4 до 8. Facebook Messenger запустил десктопное приложение для MacOS и Windows. Вышел Clubhouse для iOS. Zoom, наоборот, после скандала с Zoombombing и уязвимостями объявил, что замораживает работу над фичами и фокусируется на безопасности. Slack интегрировал в своё приложение аудиозвонки из Microsoft Teams, Zoom, Cisco и других сервисов.

  • Май-2020. Zoom снял ограничение в 40 минут на длительность группового звонка в бесплатной версии. Позже ограничение вернулось, но Zoom иногда убирает его на праздники вроде Рождества или Дня благодарения. Google выпустил бесплатную версию Google Meet для всех пользователей.

  • Июль-2020. Discord запустил видеочаты на мобильных.

  • Август-2020. Facebook добавил Zoom, Cisco Webex, BlueJeans, GoToMeeting в Facebook Portal — серию устройств для видеоконференций со следящей камерой.

  • Сентябрь-2020. Google включил технологию шумоподавления в Google Meet.

  • Октябрь-2020. В Zoom появилось сквозное шифрование.

  • Январь-2021. Илон Маск принял участие в эфире в Clubhouse. Сервис стремительно набрал популярность — даже притом что приложения для Android всё ещё не было.

  • Март-2021. WhatsApp запустил свои первые видеозвонки на десктопе, но только один на один.

  • Апрель-2021. Facebook выпустил Hotline — ответ Clubhouse, но с возможностью включить и видео.

  • Июнь-2021. Telegram пригласил в свои групповые видеозвонки: с видео могут подключиться 30 человек, ещё 1 000 получают просто трансляцию, а остальные в чате могут только слушать.

История звонков ВКонтакте началась гораздо раньше:

  • в 2012 году появились видеозвонки на вебе;

  • в 2018-м видеозвонки один на один стали доступны на мобильных;

  • в мае 2020-го мы запустили видеозвонки на 8 человек;

  • в сентябре 2020-го — видеозвонки на 128 участников и с дополнительными функциями: демонстрацией экрана, подключением по ссылке и без ограничения по длительности; 

  • в августе 2021-го выпустили десктопный клиент и сделали возможным собирать в звонках ВКонтакте до 2 048 человек;

  • в ноябре 2021-го сняли все ограничения на число участников.

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

Требования к звонкам

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

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

  • качественными;

  • с видео;

  • без ограничений по времени;

  • безопасными;

  • доступными на всех устройствах;

  • и, конечно, бесплатными.

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

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

Zoom и Google Meet, которые больше всего выросли с начала пандемии, выделяются числом участников. Скорее всего, онлайн-встречи на 1 000 человек проводятся редко, но потенциально такая возможность есть, и при необходимости за доп. плату её можно использовать.

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

Посмотрев на всё это, мы поставили такие амбициозные требования к звонкам ВКонтакте:

  • неограниченное число участников — почему бы не потягаться с лидерами в этом показателе;

  • работа на всех платформах;

  • низкое потребление серверных ресурсов — мы комплементарный сервис и не можем себе позволить то, что доступно в on-premise сегменте;

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

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

Всё это можно кратко сформулировать так: «Сделать крутые звонки».  

Минимум теории

Чтобы не только понять, как всё устроено ВКонтакте, но и лучше разобраться, как это применить у себя, начнём с базовой теории.

Любые звонки состоят из трёх уровней:

  • signaling — уровень бизнес-логики, на котором вы подключаете пользователей, координируете и устанавливаете сетевое соединение;

  • сетевой слой — транспорт и сеть;

  • уровень передачи аудио- и видеоданных.

Задачи на уровне signaling:

  • координация участников;

  • аутентификация или авторизация; 

  • распространение сообщений между участниками с гарантией порядка и подтверждением;

  • установка сетевого соединения с сервером или между участниками;

  • список участников;

  • блокировки и т. д.

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

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

Сетевой уровень

Сразу договоримся: поскольку мы говорим о звонках и нам нужны минимальные задержки, то мы всегда, когда это возможно, выбираем сетевой протокол UDP. Сейчас пытаться запустить VoIP поверх TCP кажется очень странной затеей, так как осталось около 1% сетей, которые не поддерживают UDP. А других аргументов, кроме разве что недостаточной поддержки, у TCP для потоковых данных и раньше не было.

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

Представим, что вы отправляете данные по сети, они разбиваются на пакеты и приходят клиенту-получателю с какими-то интервалами. Суммарный объём пакетов, который можно передать за единицу времени, определяет пропускную способность, или bandwidth (BW). Единица измерения пропускной способности — Кбит/с или более привычные сейчас Мбит/с.

Время между отправкой пакета и получением подтверждения (acknowledgement) о том, что он дошёл, показывает round trip time, или RTT. RTT измеряется в мс — понятно, что чем меньше, тем лучше.

Третья важная характеристика — packet loss. Показывает, сколько из отправленных пакетов потерялись по дороге, измеряется в процентах. Для наглядности все три характеристики изображены на картинке ниже. 

Теперь для разминки рассмотрим задачку. Пусть есть два клиента: Алиса и Борис. Алиса звонит Борису по аудио. Сеть у обоих хорошая, пропускная способность, например, 10 Мбит/с, packet loss 0%. Мы знаем, что при прямом p2p-соединении RTT между Алисой и Борисом обычно около 150 мс, а если звонить через сервер, то RTT от обоих клиентов до сервера равно 100 мс.

 Вопрос: где задержка будет меньше — p2p или через сервер? 

Правильный ответ

Неизвестно. 

Чтобы ответить на этот вопрос, попингуем какой-нибудь сайт — например, сайт конференции HighLoad++.

Если сделать достаточно много проб, то получится какая-то такая картина:

То есть на практике RTT — случайная величина, и разброс может быть большим. А значит, заранее точно ответить на вопрос из задачки мы не можем.

Чтобы оценить вариации задержки пакетов, используется jitter. Мы принимаем jitter за разницу между максимальной и минимальной задержкой (в спецификации RFC 3550 для RTСP описан чуть более сложный способ измерения jitter).

Jitter-эффект негативно сказывается на работе кодеков и на качестве аудио и видео. Например, обычно в VOIP-пакетах содержится по 20 мс аудио, клиент проигрывает их один за другим. Если между пачками пакетов возникла задержка в 40 мс, то клиент 40 мс будет играть тишину. Чтобы с этим бороться и равномерно работать с пакетами данных, есть jitter buffer. 

Задача jitter buffer — компенсировать задержку, вызванную jitter.

Для борьбы с packet loss есть forward error correction (FEC), или избыточное кодирование: к каждым n пакетам добавляем, например, XOR; если один из этих пакетов пропал, то через XOR его возможно восстановить. 

Естественно, это добавляет лишние накладные расходы даже в отсутствие потери пакетов, но зато позволяет справляться с фиксированным небольшим процентом packet loss. 

FEC хорошо комбинировать с NACK — negative acknowledgment: если потеряно больше пакетов, чем можно восстановить с помощью FEC, то часть из них можно перезапросить. 

Но на самом деле единственное, что поможет уменьшить RTT, — это CDN. Причём, как ни странно, CDN нужны и для p2p-звонков, и для звонков через сервер. Дело в том, что из-за особенностей работы операторов может получиться так, что p2p-звонок по VoIP между абонентами во Владивостоке может пойти через Москву. Чтобы починить это для наших пользователей, мы устанавливаем CDN, которые напрямую соединяем с разными операторами. За счёт этого мы можем запустить звонок во Владивостоке через Хабаровск или хотя бы Новосибирск — задержка наверняка будет меньше, чем через Москву. 

Всего у ВКонтакте больше 50 CDN-площадок, но для размещения конференц-серверов мы выбрали часть из них по следующим критериям:

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

  • Направления этих звонков. Совершаются ли они в рамках одной площадки или трафик идёт через другие точки.

  • Показатели RTC-соединения. При звонке через близкий CDN эти показатели должны быть лучше, чем через базовый. Например, до запуска сервера в Новосибирске 75-й перцентиль RTT между абонентами из ближайших регионов (Новосибирской области, Омской области, Алтайского края) был в районе 500 мс, потому что соединение происходило через Москву. После запуска локального сервера 75-й перцентиль RTT между этими регионами станет 250 мс (значения условные, но показывают общий смысл).

Метрики сети

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

Чтобы составить представление о нашей многомиллионной аудитории, мы обычно используем перцентили по метрикам. Ниже распределение RTT, медиана в 140 мс и 99-й перцентиль в 550 мс. То есть у 99% наших пользователей RTT меньше 550 мс. 

Характеристики сети пользователей ВКонтакте:

  • медиана: RTT — 140 мс, пропускная способность 3,3 Мбит/с, packet loss — 0,9%, packet jitter/10с — 60 мс;

  • 99-й перцентиль: RTT — 550 мс, пропускная способность 0,3 Мбит/с, packet loss — 2,2%, packet jitter/10 с — 1 400 мс. 

Такую сеть — с небольшой потерей пакетов, но большим RTT — выгоднее компенсировать с помощью FEC.

Итого: что нужно знать для стабильной работы на сетевом уровне

  • Выбирая технические решения, не смотрите на среднее — опирайтесь на медиану и перцентили (лучше 99-й).

  • Jitter компенсируется через jitter buffer и вызывает задержку.

  • С высоким RTT и недостаточной пропускной способностью можно бороться за счёт CDN.

  • Packet loss чинится через FEC/NACK.

  • Одно и то же решение для звонков, развёрнутое в разных условиях, будет показывать разные результаты.

Пайплайн звонка

Теперь соберём все этапы и посмотрим на пайплайн звонка целиком.

  • Пользователи подключаются по Signaling (WebSocket).

  • Устанавливают транспортную сессию (UDP).

  • Обмениваются доступными каждому из них кодеками для аудио и видео.

  • Кодируют с помощью этих кодеков: для аудио — Opus, видео — H.264 или VP8/VP9 в зависимости от условий. 

  • При необходимости адаптируют кодирование под пропускную способность и отправляют в сеть.

  • Чинят пропажу пакетов в динамически меняющейся сети с помощью FEC и NACK. Для аудио ещё есть Packet loss concealment в Opus.

  • Компенсируют задержки с помощью ускорения (time stretching).

  • Получается видеоконференция.

Стоит заметить, что WebRTC в каком-то виде реализует сетевой уровень и аудио- и видеопайплайн из коробки. И что мы поддерживаем WebRTC как стандарт передачи real-time данных между браузерами, поскольку хотим работать на всех платформах, включая веб.  

Но в групповых звонках его стандартных механизмов становится недостаточно, чтобы выполнить наши требования по качеству и количеству участников. Поэтому мы в некоторых случаях отходим от них — где и как, читайте в статье об аудио и о видео или смотрите в докладе). Да и в принципе WebRTC никак не реализует групповую топологию. О том, какие варианты таких топологий применяются в звонках, поговорим дальше.

Групповые звонки и топологии

Мы разобрались с сигналингом и транспортом. Осталось перейти от звонков один на один к групповым. Для этого базово есть три варианта топологии:

  • бессерверная Mesh-топология — в которой каждый участник группового звонка устанавливает прямое соединение с каждым из собеседников (так делает, например, WhatsApp);

  • через микширующий сервер MCU (Multipoint Conferencing Unit) — когда видео- и аудиопотоки от разных пользователей собираются на сервере в один и только этот объединённый поток передаётся клиенту. Вся нагрузка в MCU-топологии ложится на сервер; 

  • через сервер как ретранслятор пакетов — SFU (Selective Forwarding Unit). Все потоки кодируются на клиенте, отправляются на сервер, а сервер перенаправляет их остальным участникам звонка, которые их обратно декодируют тоже на клиенте. Большая часть нагрузки таким образом ложится на клиентские устройства.

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

Собрать в групповом видеозвонке больше 8 участников в Mesh-топологии практически невозможно.

Зато Mesh-топология выгоднее с точки зрения задержек — за счёт прямого соединения между пользователями без участия сервера.

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

Плюсы MCU. MCU экономит ресурсы на клиенте и позволяет: 

  • добавить процессинг — мы так и сделали, а также реализовали кастомное шумоподавление и voice activity detection (об этом тоже есть статья). За счёт этого повысили качество звонка;

  • реализовать трансляцию звонка и запись на сервере; 

  • подключить участников на SIP и телефонных сетях. 

Главное преимущество MCU — число участников ограничено только тем, сколько потоков вы сможете смикшировать на сервере.

SFU — компромиссный вариант. Часть нагрузки переносится с сервера на клиент (или с клиента на сервер, смотря с чем сравнивать). Исходящий трафик постоянный, но входящий растёт пропорционально числу участников в звонке.

Максимальное число участников в SFU-топологии, конечно, намного больше, чем в Mesh. Но дорогое микширование входящих потоков ложится на клиентов — и, например, браузер с WebRTC на 50 участниках, скорее всего, развалится. 

Mesh

MCU

SFU

in/out streams

3/3

1/1

3/1

input traffic

3 Мбит/сек

1 Мбит/сек

3 Мбит/сек

output traffic

3 Мбит/сек

1 Мбит/сек

1 Мбит/сек

client CPU

3*encoder + 3*decoder = 60%

1*encoder + 1*decoder = 20%

1*encoder + 3*decoder = 40%

server CPU

0

100%

10%

latency

min

max

avg

SIP/live

+

max participants

~ 8

~ 50

Ошибка выбора топологии — 1: Mesh на 8 и более участников. Если где-то прочитаете, что это возможно, не верьте. Не зря WhatsApp даже под давлением конкуренции увеличил количество участников с 4 до 8 — и не больше.  

Ошибка выбора топологии — 2: serverless forwarding broadcast вместо сервера (для ретрансмита других пользователей). Очень трудная в реализации история для real-time данных. Пусть это лучше работает там, где хорошо применимо, — в торрентах.

Формальная постановка задачи  

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

«Качественные» на нашем языке — это:

  • с минимальной задержкой, чтобы пользователи друг друга не перебивали;

  • с минимальными искажениями аудио и особенно видео;

  • с минимальным потреблением ресурсов на клиенте.

Факторы, которые влияют на качество звонка и осложняют нам жизнь:

  • характеристики сети пользователя — пропускная способность, RTT, packet loss, jitter;

  • устройство и (или) браузер;

  • количество и качество доступных клиентских ресурсов;

  • количество участников в звонке и их параметры.

Какие у нас есть ручки, чтобы с этим бороться? Мы можем повлиять на:

  • алгоритмы сетевого уровня;

  • аудио- и видеопайплайн;

  • методы и алгоритмы компенсации задержки, jitter buffer;

  • топологию групповых звонков.

Формально постановка задачи сводится к многокритериальной оптимизации:

\min_{X,V} \{задержка(X,V), искажения(X,V), потребление\ ресурсов(X,V)\}

где:

X = \{BW, RTT, PL, jitter, device(CPU, OS, GPU, resolution)|browser,\\ geo, батарейка, количество\ участников, etc.\}V = \{FEC, NACK, CDN, video/audio\ adaptation, time\ stretching, \\кодеки, топология, etc.\}

Для всего пространства X, ограниченного 99-м перцентилем и количеством участников [1, 10 000+].

Готовые решения

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

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

Jitsi — мощный open-source проект, у него много плюсов: 

  • работает из коробки;

  • бесплатный, с открытым исходным кодом;

  • есть все необходимые для запуска компоненты: jitsi-meet, jicofo, jitsi-videobridge, jibri и т. п.

Jitsi написан на Java — для нас это скорее плюс, но если вашему стеку ближе C++, то есть, например, Mediasoup. Его просто развернуть, и он реализует возможности, которые есть в WebRTC из коробки: SFU, Scalable Video Coding, transport Bandwidth Estimation, simulcast. Но это чистый SFU, без аудиомикширования и серверного транскодирования (за ответом, зачем они нужны, опять отсылаем к предыдущим статьям). То есть нагрузка на клиент будет линейно зависеть от числа участников и расти пропорционально ему.

В таблице ниже — ещё несколько вариантов и их характеристики. Если вам нужны внутренние звонки максимум на 50 участников, без особых изысков по качеству и перспектив на рост, — выбирайте готовый проект, который лучше подходит по стеку. И довольно скоро у вас будет свой конференц-сервер. 

Agora в этом сравнении участвует не совсем честно, так как это платный SDK. Но на нём работает, например, Clubhouse. И в некоторых случаях это тоже может быть лучшим вариантом, чем писать всё самим.

Для нас же, понятно, такой путь не подходил даже в начале. У нас уже была инфраструктура, на которой работали онлайн-трансляции и p2p-звонки, не хватало только SFU-сервера. Параллельно запускать и поддерживать приложение, например, на Jitsi было бы невыгодно. Многие другие компоненты тоже пришлось бы значительно перелопатить. Но некоторую часть действительно использовали: например, ice4j для анализа ICE-пакетов при установлении соединения.

Архитектура решения для звонков на неограниченное количество участников

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

У нас три основные группы серверов: 

  • Signaling-серверы обрабатывают подключение участников по WebSocket и осуществляют обмен сервисными данными между участниками. В качестве WebSocket-сервера используем Apache Tomcat, все изменения в состоянии звонка сохраняются в Cassandra. 

  • На conferencing-серверах реализован WebRTC, приём медиа.

  • Transform-серверы крутятся в облаках и подключаются к работе, когда нужно транскодировать видео (соединение с conferencing по кастомному протоколу поверх TCP).

ZooKeeper координирует, где какой звонок сейчас идёт и обрабатывается, GSLB балансирует нагрузку на conferencing.

Масштабирование одного звонка на N серверов

Предположим, Алиса звонит Борису. Если видео можно передавать как есть и ничего не нужно транскодировать, то Алиса может быть подключена к одному серверу, а Борис к другому — серверы напрямую перекинут видео, и всё (сработает только нижняя половина следующей схемы). 

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

Для отказоустойчивости закрываем наши signaling-серверы балансировщиком NFWare от NFV. Даже если что-то выпадет, то звонок не оборвётся безвозвратно. ZooKeeper оркестрирует перераспределение нагрузки и досылает всё необходимое из Cassandra.

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

Уменьшаем latency серверной архитектуры

Во-первых, если вы работаете с медиа внутри дата-центров, то включите везде TCP_NODELAY.

Во-вторых, нужно будет бороться с latency, которую генерирует сам сервер. Наш WebRTC-сервер написан на Java, что поставляет ещё один источник для jitter и увеличения latency — Java safepoints. JVM делает паузы в выполнении кода, во время которых совершает различные вспомогательные операции: GC, замену классов и методов JIT-компилятором, сбор стектрейсов и другое. Это случайные паузы, мы не можем полностью от них избавиться, но можем попытаться минимизировать их влияние на jitter.

Чтобы обеспечить низкую задержку в обработке медиаданных на Java-сервере, воспользуемся следующей установкой: чем реже и (или) короче паузы, тем лучше; несколько коротких пауз лучше, чем одна большая

Исходя из этого, два типа операций в safepoints не сильно портят latency:

  • операции, которые выполняются не регулярно, а по запросу, например thread dump;

  • операции, которые выполняются быстрее, чем длительность фрейма (обновление inline-кешей, деоптимизация кода и т. д.).

Гораздо более существенное влияние оказывают паузы для сборки мусора — они могут доходить до 50 мс. Поэтому мы заменили GC с дефолтного G1GC на Shenandoah, ориентированный на низкие паузы. В результате длительность максимальной safepoint-паузы на conference-сервере стала около 10 мс, а на transcoding-сервере — 3 мс.

Кстати, для Jitsi этот трюк с GC тоже сработает, потому что Jitsi тоже написан на Java. 

Data-channel

Кроме всех прочих действий по уменьшению задержки, мы изменили подход в передаче метаданных и используем data-channel — протокол доставки произвольных данных внутри транспортного UDP-потока вместе с SRTP аудио/видео, использующий SCTP.

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

Выше на рисунке схема без data-channel. Между signaling-сервером и streaming-сервером есть обмен данными, из-за которого возникают накладные расходы:

  • дополнительная задержка метаданных, особенно в случае CDN (signaling и streaming могут быть в разных регионах);

  • лишняя нагрузка на серверную инфраструктуру;

  • рассинхрон соединений и плохой user experience — если, например, WebSocket ушёл в реконнект, а SRTP-транспорт работает — данные доходят, метаданные нет;

  • из-за большого трафика WebSocket конкурирует за канал с SRTP, сложно координировать адаптацию под сеть.

Так выглядит схема с data-channel:

Она даёт следующие преимущества:

  • переиспользование UDP-порта, нет head-of-line blocking и NAT unbinding;

  • мультиплексирование нескольких каналов с данными;

  • возможность создавать reliable-/unreliable-каналы.

Кроме того, что data-channel помог нам достичь изначальную цель и уменьшить задержку при доставке метаданных, получили дополнительные плюсы: 

  • выше устойчивость в нестабильной сети, так как у протокола есть низкоуровневый доступ к сообщениям и буферу клиента, мы можем «схлопнуть» в одно несколько сообщений одинакового типа, когда они накапливаются в исходящем буфере (например, подсветки активных говорящих);

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

Видеотреки

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

В обычном сценарии от каждого участника звонка видео приходит на клиент со стриминг-сервера в отдельном треке:

  • с точки зрения RTP, трек — это пара SSRC: основной и retransmit;

  • с точки зрения WebRTC, под каждый трек необходимо создать MediaStreamTrack и RTCRtpReceiver.

Когда в звонок добавляется новый участник, для всех остальных клиентов создаётся дополнительная пара SSRC и происходит переобмен SDP между клиентом и стриминг-сервером. Когда участник выходит из звонка — соответствующая ему пара SSRC удаляется.

То есть чем больше участников, тем больше треков нужно держать клиентам, тем выше нагрузка и тем сильнее не хватает производительности на клиенте. В Chrome, например, звонок развалится на 50 треках, в iOS Safari — при количестве треков больше 1.

Чтобы ограничение на количество треков в одном peer connection нам не мешало, используем альтернативный подход: 

  • перед открытием peer connection клиент сообщает серверу о том, какое количество видеотреков он в состоянии поддержать;

  • сервер добавляет в SDP соответствующее количество пар SSRC;

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

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

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

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

В результате можем делать звонки на практически неограниченное число участников — столько, сколько может потребоваться собрать в реальном звонке. И всё пройдёт без проблем у клиента. Как бонус — немного экономим сетевой трафик на посылке SDP: 

  • при добавлении и удалении участников переобмен SDP не требуется;

  • даже если в звонке 10 000 участников, клиенту отсылаем в SDP гораздо меньше треков.

Must have для звонков на неограниченное число участников:

  • горизонтальное масштабирование звонка;

  • обход ограничения на число треков peer connection — мы сделали то, что называем «слоты»;

  • серверная топология SFU и микширование аудио в MCU;

  • продвинутая (нейросетевая) работа с аудио — VAD и шумоподавление.

Улучшаем алгоритмы

Вспомним, что в формальной постановке мы решаем задачу многокритериальной оптимизации: 

\min_{X,V} \{задержка(X,V), искажения(X,V), потребление ресурсов(X,V)\}

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

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

Что получилось в результате

Месячная аудитория звонков ВКонтакте — 20 млн пользователей, ежедневно на платформе совершается 6 млн звонков. У нас есть все важные функции, а главное, наша архитектура позволяет делать звонки на тысячи участников.

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

Мы видим, что у Zoom большие задержки. Но также знаем, насколько он популярен, в том числе у требовательных бизнес-пользователей. В чём же дело? 

Ответ в последней строчке: Zoom разменял задержку на работу в сетях с высоким packet loss. В ужасных условиях с packet loss 50% (это потеря каждого второго пакета!) у Zoom почти не возрастает задержка, а мы и Google Meet страдаем — и, скорее всего, звонок развалится ещё раньше.

Google Meet, в свою очередь, разменял задержку на работу в вебе и разгрузку WebRTC-клиентов.

Наше решение и по архитектуре, и по метрикам ближе к Google Meet: мы поддерживаем WebRTC, разгружаем клиентов, но боремся за каждую миллисекунду latency. 

Будущее интернет-звонков

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

  • ML Voice activity detection на градиентном бустинге;

  • ML noise suppression на нейросетях; 

  • ML-аудиокодек — Google Lyra (3 kbps);

  • ML-видеокодеки;

  • ML-улучшение изображения, лица; 

  • ML-оптимизация параметров алгоритмов адаптации — решающие деревья;

  • ML-оценка качества звонков — NISQA;

  • ML-починка пропажи пакетов — WaveNetEQ чинит 120 мс (а классический PLC только 20 мс).

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

Общий подход к решению сложных инженерных задач

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

Работая над любыми сложными инженерными решениями: 

  • определите требования, сделайте опрос или коридорное исследование;

  • изучите теорию и формализуйте задачу — если у вас есть хорошая постановка задачи, то шансы на достижение результата сильно повышаются;

  • запустите решение (можно MVP на open source);

  • cоберите метрики;

  • определите, где вы находитесь относительно конкурентов, — это очень важно (и об этом иллюстрация ниже);

  • развивайте решение, опираясь на эти знания и метрики;

  • заменяйте классические (эвристические) алгоритмы на ML.

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

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

Всегда, что бы вы ни разрабатывали, сравнивайте своё решение с продуктами конкурентов и корректируйте своё развитие, чтобы не оказаться по качеству на уровне open source, а по сложности на уровне самых продвинутых продуктов рынка.

И последнее, ответ на вопрос «Что мне с этим делать?». Варианта три:

  • встройте звонки в свой сервис;

  • пройдите собеседование в VoIP-команду и помогайте развивать один из существующих сервисов звонков;

  • начните свой стартап.

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

Теги:
Хабы:
Всего голосов 54: ↑54 и ↓0+54
Комментарии13

Публикации

Информация

Сайт
team.vk.company
Дата регистрации
Дата основания
Численность
свыше 10 000 человек
Местоположение
Россия