Масштабирование развязанного сервера реального времени вместе со стандартным веб-сервером
Скажем, у меня есть типичный веб-сервер, который обслуживает стандартные HTML-страницы для клиентов, а сервер веб-сокета, работающий рядом с ним, используется для обновлений в реальном времени (чат, уведомления и т. Д.).
Мой общий рабочий процесс заключается в том, что когда на главном сервере происходит что-то, что вызывает потребность в сообщении в реальном времени, главный сервер отправляет это сообщение на сервер реального времени (через очередь сообщений), а сервер реального времени распределяет его по любому связанному соединению.
Меня беспокоит то, что если я захочу немного увеличить масштаб и добавить еще один сервер реального времени, то, похоже, у меня есть только следующие варианты:
- Пусть главный сервер отслеживает, к какому серверу реального времени подключен клиент. Когда этот клиент получает уведомление / сообщение в чате, главный сервер пересылает это сообщение только на сервер реального времени, к которому подключен клиент. Недостатком здесь является сложность кода, так как основной сервер должен вести некоторую дополнительную бухгалтерию.
- Или вместо этого пусть основной сервер просто передает это сообщение каждому серверу реального времени; только сервер, к которому подключен клиент, на самом деле будет что-то делать с ним. Это приведет к тому, что количество пропущенных сообщений будет передано.
Я здесь пропускаю другой вариант? Я просто пытаюсь убедиться, что я не слишком далеко иду по одному из этих путей и понимаю, что делаю вещи совершенно неправильно.
4 ответа
Если сценарий
а) Главный веб-сервер выдает сообщение о действии (скажем, запись вставлена) б) Он уведомляет соответствующий сервер в реальном времени
Вы можете отделить эти два шага, используя промежуточную архитектуру pub/sub, которая пересылает сообщения получателю с отступом.
Реализация будет
1) У вас есть канал redis pub-sub, где при подключении клиента к сокету реального времени вы начинаете прослушивать этот канал
2) Когда основное приложение хочет уведомить пользователя через сервер реального времени, оно отправляет на канал сообщение, сервер реального времени получает его и пересылает его предполагаемому пользователю.
Таким образом, вы отделяете уведомление в реальном времени от основного приложения и вам не нужно отслеживать, где находится пользователь.
Проблема, которую вы описываете, заключается в общей "объединительной плате сообщений", используемой, например, в SignalR, также связанной с "обменом сообщениями разветвления" в архитектурах сообщений. При наличии объединительной платы или разветвлении каждое сообщение пересылается на каждый сервер узла сообщений, поэтому клиенты могут подключаться к любому серверу и получать сообщение. Такой подход является разумной болью, когда вам приходится поддерживать как длинные опросы, так и веб-сокеты. Однако, как вы заметили, это трата трафика и ресурсов.
Вам необходимо использовать инфраструктуру сообщений с интеллектуальной маршрутизацией, например, RabbitMQ. Взгляните на тему и обмен заголовками: https://www.rabbitmq.com/tutorials/amqp-concepts.html
Как тема обменивается сообщениями маршрутов
RabbitMQ для Windows: типы обмена
Есть множество различных структур очередей. Выберите тот, который вам нравится, но убедитесь, что вы можете иметь больше режимов обмена, чем просто прямой или разветвленный;) В конце концов, WebSocket является просто и конечной точкой для подключения к инфраструктуре сообщений. Так что если вы хотите масштабироваться, это сводится к бэкэнду, который у вас есть:)
Изменен ответ, поскольку в ответе указывалось, что серверы "main" и "realtime" представляют собой кластеры с сбалансированной нагрузкой, а не отдельные хосты.
Центральный вопрос о масштабируемости выглядит следующим образом:
Мой общий рабочий процесс заключается в том, что когда на главном сервере происходит что-то, что вызывает потребность в сообщении в реальном времени, главный сервер отправляет это сообщение на сервер реального времени (через очередь сообщений), а сервер реального времени распределяет его по любому связанному соединению.
Акцент на слове "родственный". Предположим, у вас есть 10 "главных" серверов и 50 серверов "реального времени", и на главном сервере № 5 происходит событие: какой из веб-сокетов будет считаться связанным с этим событием?
В худшем случае любое событие на любом "главном" сервере должно распространяться на все веб-сокеты. Это сложность O(N^2), которая считается серьезным ухудшением масштабируемости.
Эту сложность O(N^2) можно предотвратить, только если вы можете сгруппировать связанные соединения в группы, которые не растут с размером кластера или общим числом nr. соединений. Группировка требует памяти состояния для хранения, к какой группе (группам) принадлежит соединение.
Помните, что есть 3 способа сохранить состояние:
- глобальная память (memcached / redis / DB, ...)
- липкая маршрутизация (конфигурация балансировки нагрузки)
- клиентская память (куки, локальное хранилище браузера, ссылки / перенаправления URL)
Где вариант 3 считается наиболее масштабируемым, поскольку в нем отсутствует централизованное хранилище состояний.
Для передачи сообщений с "основного" на серверы "реального времени" этот трафик по определению должен быть намного меньше, чем трафик к клиентам. Есть также эффективные рамки для увеличения трафика в пабах и на юге.
Для нескольких серверов реального времени вы могли бы просто хранить их список на главном сервере и просто проходить через них.
Другой подход заключается в использовании балансировщика нагрузки.
По сути, у вас будет один выделенный узел для приема запросов от главного сервера, а затем этот узел балансировки нагрузки будет заботиться о выборе сервера веб-сокета / реального времени, на который будет пересылаться запрос.
Конечно, это просто переносит сложность кода с главного сервера на новый компонент, но концептуально я думаю, что он лучше и более не связан.