Масштабирование развязанного сервера реального времени вместе со стандартным веб-сервером

Скажем, у меня есть типичный веб-сервер, который обслуживает стандартные HTML-страницы для клиентов, а сервер веб-сокета, работающий рядом с ним, используется для обновлений в реальном времени (чат, уведомления и т. Д.).

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

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

  1. Пусть главный сервер отслеживает, к какому серверу реального времени подключен клиент. Когда этот клиент получает уведомление / сообщение в чате, главный сервер пересылает это сообщение только на сервер реального времени, к которому подключен клиент. Недостатком здесь является сложность кода, так как основной сервер должен вести некоторую дополнительную бухгалтерию.
  2. Или вместо этого пусть основной сервер просто передает это сообщение каждому серверу реального времени; только сервер, к которому подключен клиент, на самом деле будет что-то делать с ним. Это приведет к тому, что количество пропущенных сообщений будет передано.

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

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 способа сохранить состояние:

  1. глобальная память (memcached / redis / DB, ...)
  2. липкая маршрутизация (конфигурация балансировки нагрузки)
  3. клиентская память (куки, локальное хранилище браузера, ссылки / перенаправления URL)

Где вариант 3 считается наиболее масштабируемым, поскольку в нем отсутствует централизованное хранилище состояний.

Для передачи сообщений с "основного" на серверы "реального времени" этот трафик по определению должен быть намного меньше, чем трафик к клиентам. Есть также эффективные рамки для увеличения трафика в пабах и на юге.

Для нескольких серверов реального времени вы могли бы просто хранить их список на главном сервере и просто проходить через них.

Другой подход заключается в использовании балансировщика нагрузки.

По сути, у вас будет один выделенный узел для приема запросов от главного сервера, а затем этот узел балансировки нагрузки будет заботиться о выборе сервера веб-сокета / реального времени, на который будет пересылаться запрос.

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

Другие вопросы по тегам