СофтХабрахабр

[Перевод] Как Discord одновременно обслуживает 2,5 млн голосовых чатов с помощью WebRTC

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

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

Вместо этого здесь термин «сервер» относится к нашей серверной инфраструктуре.
Для ясности всю группу пользователей и каналов мы будем называть «группа» (guild) — в клиенте они называются «серверами».

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

У маршрутизация через серверы есть и другие преимущества: например, модерация. Маршрутизация сетевого трафика через серверы Discord также гарантирует, что ваш IP-адрес никогда не виден — и никто не запустит DDoS-атаку. Администраторы могут быстренько отключить звук и видео нарушителям.

Discord работает на многих платформах.

  • Веб (Chrome/Firefox/Edge и т. д.)
  • Автономное приложение (Windows, MacOS, Linux)
  • Телефон (iOS/Android)

Все эти платформы мы можем поддерживать только одним способом: через повторное использование кода WebRTC. Эта спецификация для коммуникаций в реальном времени включает сетевые, аудио- и видеокомпоненты. Стандарт принят Консорциумом World Wide Web и Инженерной группой по Интернету. WebRTC доступен во всех современных браузерах и как нативная библиотека для внедрения в приложения.

Таким образом, браузерное приложение полагается на реализацию WebRTC в браузере. Аудио и видео в Discord работает на WebRTC. Это означает, что некоторые функции в приложении работают лучше, чем в браузере. Однако приложения для десктопов, iOS и Android используют единый мультимедийный движок C++, построенный поверх собственной библиотеки WebRTC, специально адаптированной к потребностям наших пользователей. Например, в наших нативных приложениях мы можем:

  • Обойти приглушение громкости в Windows по умолчанию, когда все приложения автоматически приглушаются при использовании гарнитуры. Это нежелательно, когда вы с друзьями пошли в рейд и координируете действия в чате Discord.
  • Использовать собственный регулятор громкости вместо глобального микшера операционной системы.
  • Обрабатывать исходные аудиоданные для обнаружения голосовой активности и трансляции звука и видео в играх.
  • Уменьшате пропускную способность и потребление ресурсов CPU в периоды тишины — даже в самых многочисленных голосовых чатов в любой момент времени одновременно говорят всего несколько человек.
  • Обеспечить общесистемную функциональность режима «рации» (push to talk).
  • Отправлять вместе с аудио- видеопакетами дополнительную информацию (например, индикатор приоритета в чате).

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

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

Под нашим контролем

Контроль нативной библиотеки позволяет реализовать некоторые функции иначе, чем в браузерной реализации WebRTC.

В собственной библиотеке для создания обоих потоков — входящего и исходящего — используется API более низкого уровня от WebRTC (webrtc::Call). Во-первых, WebRTC полагается на протокол Session Description Protocol (SDP) для согласования аудио/видео между участниками (до 10 КБ на каждый обмен пакетами). Это адрес и порт сервера бэкенда, метод шифрования, ключи, кодек и идентификация потока (около 1000 байт). При подключении к голосовому каналу происходит минимальный обмен информацией.

webrtc::AudioSendStream* createAudioSendStream( uint32_t ssrc, uint8_t payloadType, webrtc::Transport* transport, rtc::scoped_refptr<webrtc::AudioEncoderFactory> audioEncoderFactory, webrtc::Call* call)
; config.rtp.ssrc = ssrc; config.rtp.extensions = {{"urn:ietf:params:rtp-hdrext:ssrc-audio-level", 1}}; config.encoder_factory = audioEncoderFactory; const webrtc::SdpAudioFormat kOpusFormat = {"opus", 48000, 2}; config.send_codec_spec = webrtc::AudioSendStream::Config::SendCodecSpec(payloadType, kOpusFormat); webrtc::AudioSendStream* audioStream = call->CreateAudioSendStream(config); audioStream->Start(); return audioStream;
}

Кроме того, для определения наилучшего маршрута между участниками WebRTC использует Interactive Connectivity Establishment (ICE). Поскольку у нас каждый клиент подключается к серверу, нам не нужен ICE. Это позволяет обеспечить гораздо более надёжное соединение, если вы находитесь за NAT, а также сохранить ваш IP-адрес в секрете от других участников. Клиенты периодически пингуются, чтобы файрвол сохранял открытое соединение.

Ключи шифрования устанавливаются с помощью протокола Datagram Transport Layer Security (DTLS) на основе стандартного TLS. Наконец, WebRTC использует Secure Real-time Transport Protocol (SRTP) для шифрования носителей. Встроенная библиотека WebRTC позволяет реализовать собственный транспортный уровень с помощью webrtc::Transport API.

Кроме того, мы не отправляем аудиоданные в периоды тишины — частое явление, особенно в больших чатах. Вместо DTLS/SRTP мы решили использовать более быстрое шифрование Salsa20. Это приводит к значительной экономии пропускной способности и ресурсов CPU, однако и клиент, и сервер должны быть готовы в любой момент прекратить приём данных и переписать порядковые номера аудио/видеопакетов.

Клиент и сервер обмениваются всей необходимой информацией (менее 1200 байт при обмене пакетами) — и у клиентов на основе этой информации устанавливается сессия SDP. Поскольку веб-приложение использует браузерную реализацию WebRTC API, тут нельзя отказаться от SDP, ICE, DTLS и SRTP. Бэкенд отвечает за устранение различий между десктопными и браузерными приложениями.

На бэкенде работает несколько сервисов для голосовых чатов, но мы сосредоточимся на трёх: Discord Gateway, Discord Guilds и Discord Voice. Все наши сигнальные серверы написаны на Elixir, что позволяет многократно повторно использовать код.

Через это соединение ваш клиент получает события, связанные с группами и каналами, текстовые сообщения, пакеты присутствия и т. д. Когда вы в сети, ваш клиент поддерживает соединение WebSocket к шлюзу Discord Gateway (мы называем его шлюзовым подключением WebSocket).

Клиент обновляет этот объект по шлюзовому подключению. При подключении к голосовому каналу статус подключения отображается объектом состояния голосовой связи.

defmodule VoiceStates.VoiceState do @type t :: %{ session_id: String.t(), user_id: Number.t(), channel_id: Number.t() | nil, token: String.t() | nil, mute: boolean, deaf: boolean, self_mute: boolean, self_deaf: boolean, self_video: boolean, suppress: boolean } defstruct session_id: nil, user_id: nil, token: nil, channel_id: nil, mute: false, deaf: false, self_mute: false, self_deaf: false, self_video: false, suppress: false
end

При подключении к голосовому каналу вам назначают один из серверов Discord Voice. Он отвечает за передачу звука каждому участнику канала. Все голосовые каналы в группе назначаются одному серверу. Если вы первый в чате, сервер Discord Guilds отвечает за назначение сервера Discord Voice всей группе с помощью описанного ниже процесса.

Назначение сервера Discord Voice

Каждый сервер Discord Voice периодически сообщает о своём состоянии и нагрузке. Эта информация помещается в систему обнаружения сервисов (мы используем etcd), как обсуждалось в предыдущей статье.

Когда он выбран, все объекты состояния голосовой связи (также поддерживаемые сервером Discord Guilds) передаются на сервер Discord Voice, чтобы тот мог настроить переадресацию аудио/видео. Сервер Discord Guilds следит за системой обнаружения сервисов и назначает группе наименее используемый сервер Discord Voice в данном регионе. Тогда клиент открывает второе соединение WebSocket с голосовым сервером (мы называем его голосовым соединением WebSocket), которое используется для настройки переадресации мультимедиа и индикации речи. Клиенты уведомляются о выбранном сервере Discord Voice.

Сообщение Voice Connected означает, что клиент успешно обменялся пакетами UDP с выбранным сервером Discord Voice. Когда в клиенте отображается статус Awaiting Endpoint, это означает, что сервер Discord Guilds ищет оптимальный сервер Discord Voice.

Сигнальный модуль полностью контролирует SFU и отвечает за генерацию идентификаторов потоков и ключей шифрования, перенаправление индикаторов речи и т. д. Сервер Discord Voice содержит два компонента: сигнальный модуль и блок ретрансляции мультимедиа, называемый блоком избирательной пересылки, SFU (selective forwarding unit).

Он разработан своими силами: для нашего конкретного случая SFU обеспечивает максимальную производительность и, таким образом, самую большую экономию. Наш SFU (на C++) отвечает за направление аудио- и видеотрафика между каналами. SFU также работает мостом между нативными и браузерными приложениями: он реализует транспорт и шифрование и для браузера и для нативных приложений, преобразуя пакеты в процессе передачи. При модерации нарушителей (отключение звука на сервере), их аудиопакеты не обрабатываются. SFU собирает и обрабатывает отчёты RTCP от получателей — и уведомляет отправителей, какая полоса доступна для передачи видео. Наконец, SFU отвечает за обработку протокола RTCP, который используется для оптимизации качества видео.

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

Если тот сбоит, он мгновенно перезапускается с минимальной паузой в обслуживании (несколько потерянных пакетов). Сигнальный модуль непрерывно контролирует SFU. Хотя сбои SFU редки, мы используем тот же механизм для обновления SFU без перерывов в обслуживании. Состояние SFU восстанавливается сигнальным модулем без какого-либо взаимодействия с клиентом.

Клиент также замечает сбой сервера из-за разрыва голосового соединения WebSocket, тогда он запрашивает пинг голосового сервера через шлюзовое соединение WebSocket. Когда падает сервер Discord Voice, он не отвечает на пинг — и удаляется из системы обнаружения сервисов. Затем Гильдии Discordов отправляют все объекты состояния голоса на новый голосовой сервер. Сервер Discord Guilds подтверждает сбой, консультируется с системой обнаружения сервисов и назначает группе новый сервер Discord Voice. Все клиенты получают уведомление о новом сервере и подключаются к нему для запуска настройки мультимедиа.

В этом случае мы выполняем такую же процедуру, как при сбое сервера: удаляем его из системы обнаружения сервисов, выбираем новый сервер, переводим на него все объекты состояния голосовой связи и уведомляем клиентов о новом сервере. Довольно часто серверы Discord Voice попадают под DDoS (мы видим это по быстрому увеличению входящих IP-пакетов). Когда DDoS-атака утихает, сервер возвращается обратно в систему обнаружения служб.

Сервер Discord Guilds выбирает наилучший доступный голосовой сервер в новом регионе, консультируясь с системой обнаружения сервисов. Если владелец группы решает выбрать новый регион для голоса, мы выполняем очень похожую процедуру. Клиенты разрывают текущее соединение WebSocket со старым сервером Discord Voice и создают новое соединение с новым сервером Discord Voice. Затем он переводим на него все объекты состояния голосовой связи и уведомляем клиентов о новом сервере.

Вся инфраструктура Discord Gateway, Discord Guilds и Discord Voice поддерживает горизонтальное масштабирование. Discord Gateway и Discord Guilds работают в облаке Google.

Такая инфраструктура обеспечивает большую избыточность на случай сбоев в дата-центрах и DDoS. У нас более 850 голосовых серверов в 13 регионах (размещёнными более чем в 30 дата-центрах) по всему миру. Совсем недавно добавили регион Южной Африки. Мы работаем с несколькими партнёрами и используем свои физические серверы в их дата-центрах. Благодаря инженерным усилиям как в клиентской, так и в серверной архитектуре, теперь Discord способен обслуживать одновременно более 2,6 миллиона пользователей голосового чата с исходящим трафиком более 220 Гбит/с и 120 млн пакетов в секунду.

Мы постоянно следим за качеством голосовой связи (метрики поступают с клиентской стороны на серверы бэкенда). В будущем эта информация поможет в автоматическом обнаружении и устранении деградаций.

По сравнению со звуком, видео требует значительно большей мощности CPU и пропускной способности. Хотя мы запустили видеочат и скринкасты год назад, но сейчас их можно использовать только в личных сообщениях. Решением проблемы может стать технология масштабируемого видеокодирования Scalable Video Coding (SVC), расширение стандарта H. Задача состоит в том, чтобы сбалансировать объём пропускной способности и ресурсов CPU/GPU, используемых для обеспечения наилучшего качества видео, особенно когда группа геймеров в канале находится на разных устройствах. 264/MPEG-4 AVC.

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

Показать больше

Похожие публикации

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

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

Кнопка «Наверх»