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

Во многих ситуациях для этого требуется способ связи между процессами. Например, родительский процесс может порождать рабочие процессы для выполнения некоторых вычислений, но у рабочих должен быть способ передать результат обратно родительскому процессу. У них также могут быть более строгие требования, такие как отправка журналов и информации о ходе выполнения.

Для этого требуется ИПК. IPC означает взаимодействие между процессами, стандартную проблему в программировании, имеющую множество сложных и упрощенных решений.

Node.js предоставляет базовый канал IPC из коробки. Дочерний процесс Node, созданный с помощью функции fork, может отправить сообщение родителю, вызвав process.send(msg). Сообщение здесь представляет собой структурный объект, сериализованный в JSON. Родитель может отправить сообщение обратно, используя childProcess.send(msg), где childProcess — дескриптор дочернего процесса.

Однако это имеет много-много ограничений, что делает его плохим решением во всех случаях, кроме самых простых.

Почему не process.send?

Это работает только для определенных процессов

Вы можете использовать этот метод, только если вы создаете дочерний процесс с помощью функции fork, которая позволяет создать дочерний процесс узла, выполняющий данный модуль.

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

Более того, использование нескольких процессов означает, что каждому процессу не нужно заботиться о том, на каком языке написаны другие процессы. Это одна из сильных сторон архитектуры с несколькими процессами. Но вы не сможете использовать эту силу, если используете специфичный для Node IPC.

Это не масштабируется

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

В целом детско-родительские отношения не очень гибкие. Лучшие архитектуры — это сервисные архитектуры, в которых разные процессы отвечают за разные сервисы. Эти архитектуры гораздо более масштабируемы и не зависят от мелочей, например, какой процесс породил какой. Невозможно использовать такую ​​архитектуру со стандартным IPC узла.

это один канал

Поскольку существует только один канал связи, он не позволяет отделить одно сообщение от другого, что является общим требованием. Вы можете преодолеть это, определив свой собственный протокол (который может быть простым свойством messageType), но этот метод имеет проблемы с масштабированием и может быть источником ошибок.

Не поддерживает запрос-ответ

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

Что еще там?

Мы можем использовать WAMP.

WAMP — это мощный протокол, предназначенный для связи между распределенными компонентами приложений по сети с использованием ряда различных методов передачи сообщений, включая обмен запросами и ответами. Он работает так же хорошо для связи между различными процессами в одной системе. Он поддерживает множество функций безопасности, которые нам, вероятно, не понадобятся для локального IPC (например, аутентификация и авторизация).

Несмотря на свою мощность, он действительно интуитивно понятен и прост в использовании. Мне потребовалось несколько часов, чтобы понять протокол и использовать его в довольно большом проекте, в котором раньше использовался стандартный узел IPC, и, честно говоря, я сожалел, что вообще когда-либо использовал process.send, поскольку использование WAMP сэкономило бы сотни строк кода и довольно много головной боли на этом пути.

WAMP — это открытый стандарт, не зависящий от языка с реализациями на разных языках как для клиентов, так и для сервера. В отличие от некоторых других протоколов, он не использует жестко определенные типы сообщений или файлы протоколов. Процедуры определяются на лету. Это преимущество для средних и малых вариантов использования.

Как это работает

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

WAMP поддерживает два разных шаблона сообщений:

  1. Публикация-подписка для отправки одного события нескольким одноранговым узлам.
  2. Запрос-ответ или RPC (удаленный вызов процедур) для отправки запроса и получения ответа от одного узла.

Оба они очень полезны, и вместе их можно использовать для выполнения большинства требований IPC.

WAMP обычно работает на веб-сокете, но может использовать и другие транспортные протоколы, например необработанные сокеты TCP. Использование WS в качестве транспорта позволяет настроить WAMP-клиент в браузере, что является довольно интересной функцией, хотя для нас это необязательно.

WAMP имеет множество функций, которые вам не нужно использовать, но они будут доступны, если они вам понадобятся. Такие вещи, как отмена и получение прогресса от вызовов RPC, применение фильтров к событиям и многое другое.

WAMP использует до глупости простой формат сообщений, основанный на JSON, с которым действительно легко работать.

Технические вещи

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

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

  • args или Arguments: массив позиционных аргументов. Приведены к данным JSON во время транспортировки. Например, [123, {a : 1}, []]
  • kwargs или ArgumentsKw: объект JSON, содержащий именованные аргументы. Например, {a : "hi", b : "bye"}.
  • options или иногда details: объект JSON, в основном определяющий параметры протокола. Изменяет способ обработки вашего сообщения маршрутизатором и получателем.

Это означает, что процедуры и возвращают, и принимают несколько аргументов в обеих этих формах. Это означает, что процедуры WAMP не могут, например, просто return 5;. Они должны вернуть сложный объект, одним из внутренних значений которого является 5:

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

Начиная

Чтобы начать работу с WAMP, вам нужно выбрать маршрутизатор WAMP и клиент WAMP. Настроить оба очень просто.

Выбор роутера

Существует множество реализаций маршрутизатора, в том числе:

  1. Crossbar, что-то вроде эталонной реализации, написанной на Python. Я нашел это слишком пугающим для меня.
  2. Магистраль, которая написана на Go. Я обнаружил, что в нем довольно мало документации и он немного глючит.
  3. Nexus, также написанный на Go, был моим личным выбором. Простой, но надежный и полностью документированный, а также дополнительная документация по самому протоколу WAMP.

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

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

Выберите клиента

Клиенты доступны для всех языков. Самый популярный клиент, доступный для JavaScript, — Autobahn|JS, часть набора клиентов с открытым исходным кодом в проекте Autobahn, разработанного самими создателями протокола. Это клиент, который я использовал.

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

Как это выглядит

Допустим, вы работаете над super-calc, модулем для сложных вычислений. Вы действительно хотите запустить его из изолированного процесса и общаться с ним через RPC. Возможно, вы даже захотите настроить несколько рабочих процессов super-calc, хотя для простоты предположим, что вы этого не делаете.

Предположим также, что вы хотите интегрировать WAMP в свою систему.

Вы должны начать с установления сеанса с маршрутизатором WAMP:

Затем вы должны зарегистрировать процедуры, которые предлагает модуль super-calc:

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

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

Затем вы можете начать вызывать процедуры, определенные рабочим процессом: