Разработка
Как Slack отправляет миллионы сообщений в режиме реального времени
Знаете ли вы, что наземные станции передают сигналы на спутники, находящиеся на геостационарных орбитах на высоте 22 236 миль над экватором, а затем эти сигналы транслируются на весь Североамериканский субконтинент? Сегодня спутниковые радиостанции обслуживают сотни каналов на площади 9 540 000 квадратных миль. Если вы не работаете на секретном военном объекте глубоко под землей, вы можете пользоваться спутниковым радио везде.
Подобно спутникам, Slack ежедневно отправляет миллионы сообщений по миллионам каналов в режиме реального времени по всему миру. Если мы посмотрим на трафик в типичный рабочий день, то выяснится, что большинство пользователей находятся в сети с 9 утра до 5 вечера по местному времени, с пиками в 11 утра и 2 часа дня и небольшим спадом между ними в обеденный перерыв. Хотя рабочее время в разных регионах одинаково, глядя на два пика на графике ниже, очевидно, что время прайм-тайм не одинаково: в одних регионах это после полудня, в других — до полудня. Каждая цветная линия на приведенном ниже графике представляет регион.
В этой статье мы опишем архитектуру, которую мы используем для отправки сообщений в режиме реального времени в таких масштабах. Мы подробно рассмотрим сервисы, которые отправляют сообщения в чаты и рассылают различные события онлайн-пользователям в режиме реального времени. Наши основные службы написаны на Java — это серверы каналов (Channel Servers, CS), серверы шлюзов (Gateway Servers, GS), серверы администраторов (Admin Servers, AS) и серверы присутствия (Presence Servers, PS).
Обзор серверов
Серверы каналов (CS) являются stateful и находятся в памяти, храня в себе некоторое количество истории каналов. Каждый CS сопоставлен с подмножеством каналов на основе последовательного хэширования. В пиковые моменты на каждом хосте обслуживается около 16 миллионов каналов. “Канал” в данном случае — это абстрактный термин, идентификатор которого присваивается сущности, такой как пользователь, команда, предприятие, файл или обычный канал Slack. ID канала хэшируется и сопоставляется с уникальным сервером. Каждый хост CS получает и отправляет сообщения для этих сопоставленных каналов. У одной команды Slack все каналы сопоставлены со всеми CS.
Менеджеры последовательного хэш-кольца (Consistent hash ring managers, CHARM) управляют последовательным хэш-кольцом для CS. Они очень быстро и эффективно заменяют нездоровые CS — новый CS готов обслуживать трафик менее чем за 20 секунд. Поскольку каналы команды распределены по всем CS, небольшое количество каналов команды привязано к конкретному CS. При замене сервера каналов пользователи каналов этих команд испытывают повышенную задержку в доставке сообщений в течение менее 20 секунд.
На диаграмме ниже показано, как CS регистрируются в Consul, нашем инструменте обнаружения услуг. Каждый согласованный хэш определяется и управляется CHARM, а затем AS и CS обнаруживают их, запрашивая у Consul актуальную конфигурацию.
Серверы шлюзов (GS) являются stateful и находятся в памяти. Они хранят информацию о пользователях и подписки на каналы в websocket. Этот сервис является интерфейсом между клиентами Slack и CS. В отличие от всех остальных серверов, GS развернуты в нескольких географических регионах. Это позволяет клиенту Slack быстро подключиться к узлу GS в ближайшем регионе. У нас есть механизм слива при сбоях в регионе, который плавно переключает пользователей в плохом регионе на ближайший хороший регион.
Серверы администраторов (AS) stateless и хранятся в памяти. Они являются интерфейсом между бэкендом нашего Webapp и CS. Серверы присутствия (PS) находятся в памяти и отслеживают, какие пользователи находятся онлайн. Они обеспечивают зеленые точки присутствия в клиентах Slack. Пользователи привязаны к отдельным PS. Клиенты Slack делают запросы к ним через веб-сокет, используя GS в качестве прокси для получения статуса присутствия и уведомлений об изменении присутствия. Клиент Slack получает уведомления о присутствии только для подмножества пользователей, которые видны на экране приложения в любой момент времени.
Настройка клиента Slack
Каждый клиент Slack имеет постоянное websocket-соединение с серверами Slack, чтобы получать события в реальном времени для поддержания своего состояния. Клиент устанавливает websocket-соединение, как показано ниже.
При загрузке клиент получает токен пользователя и информацию о настройке websocket-соединения из бэкенда Webapp. Webapp — это кодовая база Hacklang, в которой размещены все API, вызываемые нашими клиентами Slack. Этот сервис также включает код JavaScript, который рендерит клиентов Slack. Клиент инициирует вебсокетное соединение с ближайшим краевым регионом. Envoy пересылает запрос в GS. Envoy — это пограничный и сервисный прокси с открытым исходным кодом, разработанный для облачных приложений. Envoy используется в Slack в качестве решения для балансировки нагрузки для различных сервисов и завершения TLS. GS получает информацию о пользователе, включая все его каналы, из Webapp и отправляет первое сообщение клиенту. Затем GS асинхронно подписывается на все серверы каналов, которые содержат эти каналы на основе последовательного хэширования. Теперь клиент Slack готов отправлять и получать сообщения в реальном времени.
Отправьте сообщений миллионам клиентов в режиме реального времени
После настройки клиента каждое сообщение, отправленное в канале, транслируется всем клиентам, находящимся онлайн в канале. Наша статистика сообщений показывает, что коэффициент умножения для трансляции сообщений различается по регионам, причем в некоторых регионах он выше, чем в других. Это может быть связано с несколькими факторами, включая размер команд в этих регионах. На графике ниже показано количество полученных сообщений и количество переданных сообщений по нескольким регионам.
Давайте рассмотрим, как сообщение передается всем онлайн-клиентам. После настройки вебсокета, как говорилось выше, клиент обращается к нашему Webapp API для отправки сообщения. Webapp затем отправляет это сообщение в AS. AS смотрит на ID канала в этом сообщении, обнаруживает CS через последовательное хэш-кольцо и направляет сообщение на соответствующий CS, который принимает сообщения в реальном времени для этого канала. Когда CS получает сообщение для этого канала, он рассылает его всем GS по всему миру, которые подписаны на этот канал. Каждый GS, получивший сообщение, отправляет его каждому подключенному клиенту, подписанному на данный идентификатор канала.
Ниже показан путь сообщения от клиента через наш стек. В следующем примере клиент Slack A и B находятся в одном краевом регионе, а C — в другом. Клиент A отправляет сообщение, а клиенты B и C его получают.
События
Помимо сообщений чата, существует еще один особый вид сообщений, называемый событием. Событие — это любое обновление, которое клиент получает в режиме реального времени и которое изменяет состояние клиента. Существуют сотни различных типов событий, проходящих через наши серверы. Например, пользователь отправляет реакцию на сообщение, добавляется закладка или пользователь присоединяется к каналу. Эти события проходят аналогичный путь, что и простое сообщение чата, показанное выше.
Посмотрите на график доставки сообщений ниже. График скачет через регулярные промежутки времени. Что может вызвать эти скачки? Оказывается, события, отправленные для напоминаний, запланированных сообщений и событий календаря, обычно происходят в начале часа, что объясняет регулярные всплески трафика.
Теперь давайте рассмотрим другой тип событий, называемый переходными событиями. Это категория событий, которые не сохраняются в базе данных и передаются через несколько иной поток. Одним из таких событий является ввод пользователем текста в канале или документе.
Ниже приведена диаграмма, показывающая этот сценарий. Опять же, клиенты Slack A и B находятся в одном краевом регионе, а C — в другом. Клиент Slack A набирает текст в канале, о чем сообщается другим пользователям B и C в канале. Клиент A отправляет это сообщение через websocket в GS. GS смотрит на ID канала в сообщении и направляет его соответствующему CS на основе согласованного хэш-кольца. Затем CS отправляет сообщение всем GS по всему миру, подписанным на этот канал. Каждый GS, получив это сообщение, рассылает его всем пользователям, подписанным на этот канал.
Что дальше
Наши серверы обслуживают десятки миллионов каналов на каждом хосте, десятки миллионов подключенных клиентов, и наша система доставляет сообщения по всему миру за 500 мс. Благодаря линейной масштабируемости нашей текущей архитектуры, наши прогнозы показывают, что мы можем обслуживать гораздо больше клиентов. Однако всегда есть место для совершенствования, и мы стремимся расширить нашу архитектуру для обслуживания еще больших масштабов. Если эта работа кажется вам интересной, присоединяйтесь к нам — у нас открыта вакансия!
Наконец, огромное спасибо всем, кто внес свой вклад в создание этой архитектуры, а также Сергею Мурахову за рецензию и отзывы на эту статью.
-
Видео и подкасты для разработчиков1 месяц назад
Lua – идеальный встраиваемый язык
-
Новости1 месяц назад
Poolside, занимающийся ИИ-программированием, привлек $500 млн
-
Новости1 месяц назад
Видео и подкасты о мобильной разработке 2024.40
-
Новости1 месяц назад
Видео и подкасты о мобильной разработке 2024.41