Как эффективно публиковать sh подписчикам graphql при масштабировании серверной части - PullRequest
3 голосов
/ 13 июля 2020

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

Поскольку я не сохраняю какое-либо состояние в бэкэнде, я попытался решить проблему с помощью реализации Redis PUB / SUB. Когда микросервис получает событие, он публикует sh в бэкэндах.

В резолвере подписок серверной части у меня есть

webhookCalled: {
    subscribe: withFilter(
            () => pubsubMyEvent.asyncIterator([MY_EVENT]),
            (payload, _, context) => {
                return context.dataValues.id == payload.userid;
            }
        )
    }

В приведенном выше коде я пытаюсь отфильтровать подписки, которые полезная нагрузка не адресована. Я не уверен, насколько дорогая процедура withFilter. Когда я получаю PUB от Redis, я звоню

pubsubMyEvent.publish(MY_EVENT, { myEventData });

Что мне здесь не нравится, так это то, что каждый ответ будет обрабатывать (publish(...)) все события, даже если в конце только один бэкэнд действительно отправит сообщение о подписке для клиента graphql.

Вопрос: как я могу эффективно обрабатывать отправку событий клиентам подписки на graphql, имея масштабируемую внутреннюю часть? Возможно, чтобы не беспокоить все серверные копии, когда нужно уведомить об одном подключении к веб-сокету. Должен ли я отслеживать всех подключенных клиентов в Redis, чтобы Redis знал, где подключен каждый клиент подписки на graphql?

введите описание изображения здесь

Ответы [ 2 ]

0 голосов
/ 22 июля 2020

Самый простой способ обеспечить масштабируемость бэкэнда, вероятно, создать канал redis для каждого бэкенда. начал обрабатывать больше трафика c. Мы присваиваем ему новый идентификатор, предположим, здесь 3, это может быть uuid, чтобы легко избежать конфликтов. Он подписывается на канал channel-3.

Клиенты случайным образом подключаются к бэкэнду. Предположим, новый клиент подключается через веб-сокет к новому бэкэнду 3. При подключении бэкэнд сообщает свой идентификатор. Это означает, что соответствующий канал Redis будет channel-3. Если другим службам нужна эта связь между пользователем и его каналом, ее также можно сохранить в redis. Вызов веб-перехватчика для нового пользователя отправляет новое сообщение в redis в channel-3. Серверная часть 3, подписавшись на этот канал, прочитает сообщение и отправит его соответствующему пользователю с помощью указанного вами фильтра graphql.

Уменьшить масштаб

  • Бэкэнд 3 больше не используется, поэтому мы его закрыли. Все соединения веб-сокетов с клиентами закрыты.
  • Клиенты, которые потеряли соединение, должны повторно подключиться к бэкэнду, это повторная попытка c в коде внешнего интерфейса. Теперь это тот же процесс, что и на шаге 2 увеличения масштаба.

Дополнительное примечание

Если у вас нет большого количества входящих сообщений от redis, что, вероятно, является обычным случаем , вы можете справиться с этим с меньшими трудностями. Вы можете решить хранить связанные идентификаторы в каждом бэкэнде в set. Операции поиска и добавления имеют сложность O(1), поэтому они будут очень быстрыми. Перед вызовом webhookCalled вы фильтруете только подключенных клиентов. Поскольку фильтр подписки graphql имеет линейную сложность в зависимости от количества подписчиков, здесь он не изменится, потому что это число будет таким же.

Ответ Даниэля Риччи - еще один способ справиться с этим. чехол.

0 голосов
/ 16 июля 2020

Отправка опубликованного события всем клиентам, подписанным на один и тот же канал, имеет характер redis.

Возможное решение, сохраняющее ту же архитектуру, может заключаться в использовании разных каналов для каждого пользователя, а не только один канал. Поддерживаемый должен подписаться на канал MY_EVENT + user.uuid при подключении нового пользователя и отказаться от подписки на тот же канал после отключения пользователя. С другой стороны, служба после вызова webhook должна публиковать sh не на глобальном канале, а на канале MY_EVENT + user.uuid.

...