Рубрики
Без рубрики

Node.js Детские процессы: все, что вам нужно знать

Как использовать Spawn (), EXEC (), Execfile () и Fork () (), обновление: эта статья теперь является частью моей книги «Node.js за пределы основы». Прочитайте обновленную версию этого контента и больше о узле на jscomplete.com/node-beyyond-basics.single-threaded, не блокирующая производительность в Node.js работает отлично подходит для одного процесса. Но

Автор оригинала: Samer Buna.

Как использовать Spawn (), EXEC (), Execfile () и FORK ()

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

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

Тот факт, что Node.js работает в одной поток, не означает, что мы не можем воспользоваться несколькими процессами и, конечно же, несколько машин.

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

Обратите внимание, что вам нужно хорошее понимание Node.js События и Потоки Прежде чем прочитать эту статью. Если вы еще этого не сделали, я рекомендую прочитать эти две другие статьи, прежде чем читать этот:

Понимание у Node.js архитектура, ориентированная на события Большинство объектов узла – как HTTP-запросы, ответы и потоки – реализовать модуль Eventemitter, чтобы они могли …

Потоки: все, что вам нужно знать Node.js Потоки имеют репутацию для того, чтобы быть трудно работать, и еще сложнее понять. Ну у меня хорошие новости …

Модуль дочерних процессов

Мы можем легко раскрутить детский процесс, используя узел Child_Process Модуль и эти детские процессы могут легко общаться друг с другом с помощью системы обмена сообщениями.

Child_Process Модуль позволяет допускать доступ к функциям операционной системы, запустив любую системную команду внутри A, хорошо, детский процесс.

Мы можем контролировать этот ввод ввода ребенка и прослушать его выходной поток. Мы также можем контролировать аргументы, которые будут переданы в базовую команду ОС, и мы можем сделать все, что мы хотим с выходом этой команды. Например, мы можем, например, труба вывода одной команды в качестве входа в другую (как мы делаем в Linux), так как все входы и выходы этих команд могут быть представлены нам, используя Node.js Streams Отказ

Обратите внимание, что примеры, которые я буду использоваться в этой статье, все на основе Linux. В Windows вам необходимо переключить команды, которые я использую со своими альтернативами Windows.

Есть четыре разных способа создания дочернего процесса в узле: Spawn () , вилка () , EXEC () и execfile () Отказ

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

Породительные дочерние процессы

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

const { spawn } = require('child_process');

const child = spawn('pwd');

Мы просто разрушаем Спон функционировать из Child_Process Модуль и выполните его с помощью команды OS в качестве первого аргумента.

Результат выполнения Спон Функция (Child Child объект выше) – Детский труд экземпляр, который реализует Eventemitter API Отказ Это означает, что мы можем зарегистрировать обработчики для событий на этом дочернем объекте напрямую. Например, мы можем сделать что-то, когда дочерний процесс выходит, зарегистрировав обработчик для Выход событие:

child.on('exit', function (code, signal) {
  console.log('child process exited with ' +
              `code ${code} and signal ${signal}`);
});

Обработчик выше дает нам выход Код Для детского процесса и сигнал Если таковые имеются, это было использовано для прекращения дочернего процесса. Это сигнал Переменная NULL, когда дочерний процесс выходит нормально.

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

  • Отключить Событие испускается, когда родительский процесс вручную вызывает Child.disconnect функция.
  • Ошибка . Мероприятие испускается, если процесс не может быть заметен или убит.
  • Закрыть событие испускается, когда STDIO Потоки детского процесса закрываются.
  • сообщение Событие является самой важной. Это испускается, когда ребенок использует Process.send () Функция для отправки сообщений. Вот как родительские/дочерние процессы могут общаться друг с другом. Мы увидим пример этого ниже.

Каждый ребенок также получает три стандарта STDIO Потоки, которые мы можем получить доступ к использованию Child.Stdin , Child.Stdout и Child.Stderr Отказ

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

Поскольку все потоки – эмиттеры событий, мы можем слушать разные события на тех STDIO Потоки, которые прикреплены к каждому дочерному процессу. В отличие от нормального процесса, хотя, в детском процессе, stdout / Стдерр Потоки – это читаемые потоки, пока stdin Поток является пиремным. Это в основном обратное описание этих типов, как найдено в основном процессе. События, которые мы можем использовать для тех потоков, являются стандартными. Самое главное, на читаемых потоках мы можем слушать данные Событие, которое будет иметь вывод команды или любая ошибка, столкнувшись при выполнении команды:

child.stdout.on('data', (data) => {
  console.log(`child stdout:\n${data}`);
});

child.stderr.on('data', (data) => {
  console.error(`child stderr:\n${data}`);
});

Над вышеупомянутыми обработчиками будут регистрировать оба случая основного процесса stdout и Стдерр Отказ Когда мы выполняем Спон Функция выше, вывод PWD Команда напечатана, а детский процесс выходит с кодом 0 , что означает, что ошибка не произошла.

Мы можем передавать аргументы команда, которая выполняется Спон Функция с использованием второго аргумента Спон Функция, которая представляет собой массив всех аргументов, которые будут переданы в команду. Например, чтобы выполнить Найти Команда в текущем каталоге с -тип е Аргумент (только перечисление файлов), мы можем сделать:

const child = spawn('find', ['.', '-type', 'f']);

Если во время выполнения команды происходит ошибка, если мы дадим найти неверный пункт назначения выше, Child.Stderr данные Обработчик событий будет вызван и Выход Обработчик событий сообщит о выходе из кода 1 , что означает, что произошла ошибка. Значения ошибок фактически зависят от хост-ОС и типа ошибки.

Детский процесс stdin является пиремным потоком. Мы можем использовать его, чтобы отправить команду некоторого ввода. Как и любой пирентный поток, самый простой способ потреблять его используют труба функция. Мы просто проводят читаемый поток в пиковый поток. С основного процесса stdin это читаемый поток, мы можем трусить это в детский процесс stdin ручей. Например:

const { spawn } = require('child_process');

const child = spawn('wc');

process.stdin.pipe(child.stdin)

child.stdout.on('data', (data) => {
  console.log(`child stdout:\n${data}`);
});

В приведенном выше примере дочерний процесс вызывает WC Команда, которая подсчитывает линии, слова и символы в Linux. Затем мы выбираем основной процесс stdin (который является читаемым потоком) в детский процесс stdin (который является пиремным потоком). Результатом этой комбинации состоит в том, что мы получаем стандартный входной режим, где мы можем ввести что-то, и когда мы ударим Ctrl + D , что мы набрали, будут использоваться в качестве ввода WC команда.

Мы также можем трусить стандартное вход/вывод нескольких процессов друг на друга, так же, как мы можем сделать с помощью команд Linux. Например, мы можем трусить stdout из Найти Команда на stdin из WC Команда для подсчета всех файлов в текущем каталоге:

const { spawn } = require('child_process');

const find = spawn('find', ['.', '-type', 'f']);
const wc = spawn('wc', ['-l']);

find.stdout.pipe(wc.stdin);

wc.stdout.on('data', (data) => {
  console.log(`Number of files ${data}`);
});

Я добавил -l Аргумент для WC Команда, чтобы сделать это подсчитать только строки. При выполнении код выше будет выводить счет всех файлов во всех каталогах под текущим.

Синтаксис оболочки и функция EXEC

По умолчанию Спон Функция не создает оболочка Чтобы выполнить команду, которую мы передаем в него. Это делает его немного более эффективным, чем exec Функция, которая создает оболочку. exec Функция имеет одно важное значение. Это буферы Сгенерированный вывод команды и передает все выходное значение к функции обратного вызова (вместо использования потоков, что является чем Spawn делает).

Вот предыдущий Найти |. WC Пример реализован с exec функция.

const { exec } = require('child_process');

exec('find . -type f | wc -l', (err, stdout, stderr) => {
  if (err) {
    console.error(`exec error: ${err}`);
    return;
  }

  console.log(`Number of files ${stdout}`);
});

Так как exec Функция использует оболочку для выполнения команды, мы можем использовать Shell Syntax прямо здесь используя оболочку труба особенность.

Обратите внимание, что использование синтаксиса оболочки поставляется на Риск безопасности Если вы выполняете любой вид динамического ввода снаружи. Пользователь может просто сделать атаку впрыска команды, используя символы синтаксиса оболочки, как; и $ (например, команда + '; RM -RF ~' )

exec Функция буферы вывода и передает его в функцию обратного вызова (второй аргумент на Exec ) как stdout аргумент там. Это stdout Аргумент – это выходная команда, которую мы хотим распечатать.

exec Функция – хороший выбор, если вам нужно использовать синтаксис оболочки, и если размер данных, ожидаемых от команды, небольшой. (Помните, Exec Буксируют все данные в памяти, прежде чем вернуть ее.)

Спон Функция является гораздо лучшим выбором, когда размер данных, ожидаемых от команды, велик, поскольку данные будут переданы стандартными объектами IO.

Мы можем заставить порочную детский процесс наследует стандартные объекты IO своих родителей, если мы хотим, но и, что более важно, мы можем сделать Спон Функция использует синтаксис оболочки также. Вот одинаково Найти |. WC Команда реализована с Спон Функция:

const child = spawn('find . -type f | wc -l', {
  stdio: 'inherit',
  shell: true
});

Из-за Stdio: «Наследовать» Вариант выше, когда мы выполняем код, детский процесс наследует основной процесс stdin , stdout и Стдерр Отказ Это приводит к тому, что обработки данных дочерних процессов обработки данных будут запущены на главном Process.Stdout Поток, сделав скрипт выводить результат прямо сейчас.

Из-за Shell: True Вариант выше, мы смогли использовать синтаксис оболочки в прошедшей команде, так же, как мы сделали с exec Отказ Но с этим кодом мы все еще получаем преимущество потокового потока данных, которые Спон Функция дает нам. Это действительно лучший из обоих миров.

Есть несколько других хороших вариантов, которые мы можем использовать в последнем аргументе Child_Process Функции кроме оболочка и STDIO Отказ Мы можем, например, использовать CWD Возможность изменить рабочий каталог сценария. Например, вот одинаковые привыки Count-All-файлы, сделанные с помощью Спон Функция с использованием оболочки и с рабочим каталогом, установленным в папку «Мои загрузки». CWD Опция здесь сделает скрипт подсчитать все файлы, которые у меня есть в ~/Загрузки :

const child = spawn('find . -type f | wc -l', {
  stdio: 'inherit',
  shell: true,
  cwd: '/Users/samer/Downloads'
});

Другой вариант, который мы можем использовать, это env Возможность указать переменные среды, которые будут видны новому дочерному процессу. По умолчанию для этой опции есть Process.env который дает любой доступ к команде к текущей среде процедуры. Если мы хотим переопределить это поведение, мы можем просто передать пустой объект как env Опция или новые значения, которые следует рассматривать как единственным переменным среды:

const child = spawn('echo $ANSWER', {
  stdio: 'inherit',
  shell: true,
  env: { ANSWER: 42 },
});

Приведенное выше команда Echo не имеет доступа к переменным среды родительского процесса. Это не может, например, доступ $ Главная , но это может получить доступ $ Ответ Потому что он был передан как пользовательская вариабельная среда через env вариант.

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

Предполагая, что у нас есть файл Timer.js который сохраняет петлю событий:

setTimeout(() => {  
  // keep the event loop busy
}, 20000);

Мы можем выполнить его на заднем плане, используя Отсоединены вариант:

const { spawn } = require('child_process');

const child = spawn('node', ['timer.js'], {
  detached: true,
  stdio: 'ignore'
});

child.unref();

Точное поведение отдельных дочерних процессов зависит от ОС. В Windows откровенного дочернего процесса будет иметь свое собственное окно консоли, в то время как в Linux дочернем процессе будет сделан лидер новой группы и сеанса процессов.

Если Unref Функция вызывается в отдельном процессе, родительский процесс может выйти независимо от ребенка. Это может быть полезно, если ребенок выполняет длительный процесс, но держать его на фоне ребенка STDIO Конфигурации также должны быть независимыми от родителя.

Приведенный выше пример запустит скрипт узла ( Timer.js ) на заднем плане, отсоединяя, а также игнорируя его родитель STDIO Дескрипторы файлов, так что родитель может завершиться, пока ребенок продолжает работать в фоновом режиме.

Функция execfile.

Если вам нужно выполнить файл без использования оболочки, Execile Функция – это то, что вам нужно. Это ведет себя точно так, как exec Функция, но не использует оболочку, что делает его немного более эффективным. В Windows некоторые файлы не могут быть выполнены самостоятельно, например .bat или .cmd файлы. Эти файлы не могут быть выполнены с помощью Execile и либо exec или Спон С помощью оболочки настроен на true, требуется для их выполнения.

Функция * синхронизации

Функции Спон , exec и Execile от Child_Process Модуль также обладает синхронными блокирующими версиями, которые будут ждать, пока дочерний процесс не выходит.

const { 
  spawnSync, 
  execSync, 
  execFileSync,
} = require('child_process');

Эти синхронные версии потенциально полезны при попытке упростить задачи сценариев или любые задачи обработки запуска, но их следует избегать иначе.

Функция FORK ()

вилка Функция – это вариант Спон Функция для нерестовых узлов. Самая большая разница между Спон и вилка Соответствует ли канал связи для дочернего процесса при использовании вилка Итак, мы можем использовать Отправить Функция на раздвоенном процессе вместе с глобальным Процесс Сам объект для обмена сообщениями между родительскими и разветвленными процессами. Мы делаем это через Eventemitter Модульный интерфейс. Вот пример:

Родительский файл, parent.js :

const { fork } = require('child_process');

const forked = fork('child.js');

forked.on('message', (msg) => {
  console.log('Message from child', msg);
});

forked.send({ hello: 'world' });

Детский файл, Child.js :

process.on('message', (msg) => {
  console.log('Message from parent:', msg);
});

let counter = 0;

setInterval(() => {
  process.send({ counter: counter++ });
}, 1000);

В родительском файле выше мы вилее Child.js (который будет выполнять файл с помощью команды Node ), а затем мы слушаем сообщение событие. сообщение Мероприятие будет испущено всякий раз, когда ребенок использует Процесс.send , что мы делаем каждую секунду.

Передать сообщения от родителя к ребенку, мы можем выполнить Отправить Функция на самом разложенном объекте, а затем, в детском скрипте, мы можем слушать сообщение Событие на глобальном Процесс объект.

При выполнении parent.js Файл выше, это сначала отправит {Здравствуйте: «Мир»} Объект, который будет напечатан разведочным дочерним процессом, а затем разведочный детский процесс отправит увеличенное значение счетчика каждую секунду, чтобы напечатать родительский процесс.

Давайте сделаем более практичный пример о вилка функция.

Допустим, у нас есть HTTP-сервер, который обрабатывает две конечные точки. Одна из этих конечных точек ( /Compute ниже) вычисляется дорого и займет несколько секунд для завершения. Мы можем использовать длинную для цикла, чтобы имитировать, что:

const http = require('http');

const longComputation = () => {
  let sum = 0;
  for (let i = 0; i < 1e9; i++) {
    sum += i;
  };
  return sum;
};

const server = http.createServer();

server.on('request', (req, res) => {
  if (req.url === '/compute') {
    const sum = longComputation();
    return res.end(`Sum is ${sum}`);
  } else {
    res.end('Ok')
  }
});

server.listen(3000);

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

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

Сначала мы переместим весь долгоценка Функция в свой собственный файл и заставить его вызывать эту функцию при проведении указания через сообщение из основного процесса:

В новом compute.js файл:

const longComputation = () => {
  let sum = 0;
  for (let i = 0; i < 1e9; i++) {
    sum += i;
  };
  return sum;
};

process.on('message', (msg) => {
  const sum = longComputation();
  process.send(sum);
});

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

const http = require('http');
const { fork } = require('child_process');

const server = http.createServer();

server.on('request', (req, res) => {
  if (req.url === '/compute') {
    const compute = fork('compute.js');
    compute.send('start');
    compute.on('message', sum => {
      res.end(`Sum is ${sum}`);
    });
  } else {
    res.end('Ok')
  }
});

server.listen(3000);

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

После того, как разведочный процесс выполнен с той длительной операцией, она может отправить свой результат обратно в родительский процесс, используя Процесс.send Отказ

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

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

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

Это все, что у меня есть для этой темы. Спасибо за чтение! До скорого!

Изучение реагировать или узел? Оформить заказ моих книг: