Горизонтальное масштабирование и GraphQL в среде Node.js

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

В статье я прочитал, что можно создавать приложения в реальном времени с использованием GraphQL с «подписками», и, кроме того, это простой в использовании протокол, который имеет то преимущество, что сводит к минимуму поиск объектов в оба конца и, следовательно, использует меньше ресурсов.

Но что, если нам нужно добавить в систему новый сервер/узел для горизонтального масштабирования? Возможно ли это с помощью GraphQL?

В качестве примера реализации веб-сокетов, допускающей горизонтальное масштабирование, можно привести SocketCluster. Интересно, может ли приложение, разработанное только с помощью GraphQL, масштабироваться на нескольких узлах/машинах, или оно должно использоваться с другой структурой, такой как SocketCluster, для достижения этой цели.


person Strider    schedule 02.05.2018    source источник


Ответы (1)


Коротко - да. Мы сделали это, и это работает очень хорошо.

Хитрость в том, что вы должны думать глубже, чем просто рабочие приложения API, когда дело доходит до горизонтального масштабирования. Если вам нужна архитектура push-уведомлений, она должна быть асинхронной с самого начала.

Для этого мы использовали системы очередей, а именно RabbitMQ.

Представьте себе такой сценарий формирования отчета, который может занять до 10 минут:

  1. Клиент подключается к нашему GraphQL API (экземпляр 1) через WebSocket
  2. Клиент отправляет команду для создания отчета через WebSocket
  3. API генерирует токен для команды и помещает команду для создания отчета в CommandQueue (в RabbitMQ), возвращая токен клиенту.
  4. Клиент подписывается на события результата своей команды, используя токен
  5. Какой-то backend Worker подхватывает команду и выполняет процедуру формирования отчета
  6. За это время GraphQL API (экземпляр 1) умирает
  7. Клиент автоматически повторно подключается к GraphQL API (экземпляр 2)
  8. Клиент продлевает подписку с ранее приобретенным токеном
  9. Рабочий процесс выполнен, результаты в EventsQueue (RabbitMQ)
  10. ВСЕ наши экземпляры GraphQL получают информацию о ReportGenerationDoneEvent и проверяют, прослушивает ли кто-нибудь его токен.
  11. GraphQL API (экземпляр 2) видит, что клиент ожидает результатов. Отправляет результаты через веб-сокеты.
  12. GraphQL API (экземпляры 3-100) игнорирует ReportGenerationDoneEvent.

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

И что в этом гениального, вы получаете хорошее горизонтальное масштабирование, воспроизводимость событий (повторные попытки), разделение задач (клиент, API, рабочие процессы), как можно быстрее отправляете данные клиенту, и, как вы упомянули, вы делаете не тратить пропускную способность на запросы are we done yet?.

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

Хорошая мысль о SocketCluster. Это оптимизировало бы шаг 10 в приведенном выше сценарии, но на данный момент мы не видим никаких проблем с производительностью при трансляции ReportGenerationDoneEvent на весь кластер API. При большем количестве экземпляров или многорегиональной архитектуре это было бы обязательно, так как это позволило бы лучше масштабировать и сегментировать.

Важно понимать, что SocketCluster работает на уровне связи (WebSockets), но уровень логического API (GraphQL) выше этого. Чтобы сделать подписку GraphQL, вам просто нужно использовать протокол связи, который позволяет вам передавать информацию пользователю, и WebSockets позволяют это.

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

Если вас больше интересует асинхронная внутренняя архитектура, которую я использовал выше, прочитайте шаблоны CQRS и Event Sourcing.

person Marcin Natanek    schedule 02.05.2018
comment
Спасибо за ваш ответ. Просто для лучшего понимания: когда вы говорите об использовании GraphQL API через Websocket, вы имеете в виду, что вы инициируете подключение к сокету с помощью Websocket, а затем обрабатываете обмен данными с GraphQL? Я все еще новичок в этих технологиях, поэтому, пожалуйста, поправьте меня, если я ошибаюсь: разве подписки GraphQL и соединения WebSocket не эквивалентны? В чем преимущество использования GraphQL с SocketCluster, если оно не предназначено для масштабирования и сегментирования? - person Strider; 03.05.2018
comment
Когда вы настраиваете свой проект для использования WebSockets, вы теоретически можете отказаться от всех HTTP-запросов GraphQL и просто использовать WebSockets для связи. Чтобы сделать подписку GraphQL, вам просто нужно использовать протокол связи, который позволяет вам передавать информацию пользователю, и WebSockets позволяют это. Таким образом, они не эквивалентны, они работают на разных уровнях — подписка GQL находится на логическом уровне более высокого уровня и использует WebSocket для передачи данных. Кроме того, использование SocketCluster в основном даст вам то, о чем вы только что сказали, — масштабирование и сегментирование. - person Marcin Natanek; 03.05.2018
comment
В комбинированном дизайне REST и Websockets (см. вопрос stackoverflow.com/a/43971625/5486116) многие рекомендуют реализовать решение, в котором мы подписываемся на изменения, только если нам это нужно. Я думаю, что это решение потребляет меньше ресурсов и дает лучшую производительность ... что, если мы хотим применить ту же логику при объединении веб-сокетов с GraphQL: мы общаемся в основном через веб-сокет (SocketCluster), и для любого обмена данными мы используем GraphQL, и конечно, SocketCluster будет в основном отвечать за масштабируемость и сегментацию. Это хороший дизайн? - person Strider; 03.05.2018
comment
Я думаю, что это хороший выбор дизайна, но не забывайте повторять реализацию. Используйте SocketCluster только в том случае, если вы планируете открывать много сокетов в любой момент времени. Также да, вы должны подписываться только при необходимости, потому что WebSocket отслеживает состояние и требует управления и сердцебиения. - person Marcin Natanek; 03.05.2018
comment
Еще раз спасибо за ваше руководство. Что вы думаете о том, чтобы добавить резюме к своему ответу, чтобы больше людей могли извлечь пользу из того, что вы сказали мне в своих комментариях? - person Strider; 03.05.2018