Дочерний процесс Node.js и сценарии приложений

Node.js
Дочерний процесс Node.js и сценарии приложений

задний план

так какons(пакет Alibaba Cloud RocketMQ) упакован на основе C. Он не поддерживает создание экземпляров нескольких производителей и потребителей в одном процессе.Для решения этой проблемы используется подпроцесс Node.js.

Ямы, обнаруженные во время использования

  • Релиз: после того, как управление процессами закрывает основной процесс, дочерний процесс становится процессом операционной системы (pid равен 1).

несколько решений

  • Рассматривайте дочерний процесс как независимый запущенный процесс, записывайте pid, а при публикации управление процессом закрывает основной процесс и закрывает дочерний процесс
  • Основной процесс прослушивает событие завершения работы и активно закрывает свои собственные подпроцессы.

Тип дочернего процесса

  • спавн: выполнить команду
  • exec: выполнить команду (новая оболочка)
  • execFile: выполнить файл
  • fork: выполнить файл

Общие события подпроцесса

  • exit
  • close
  • error
  • message

Существует разница между close и exit: close — это событие, инициируемое при закрытии потока данных, а exit — это событие, инициируемое при завершении дочернего процесса. Поскольку несколько подпроцессов могут совместно использовать один и тот же поток данных, событие закрытия может не запускаться при завершении подпроцесса, поскольку в это время поток данных используют другие подпроцессы.

поток данных дочернего процесса

  • stdin
  • stdout
  • stderr

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

spawn

spawn(command[, args][, options])

Выполнение команды и возврат различных результатов выполнения через поток данных.

Основное использование

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

const child = spawn('find', [ '.', '-type', 'f' ]);
child.stdout.on('data', (data) => {
    console.log(`child stdout:\n${data}`);
});

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

child.on('exit', (code, signal) => {
    console.log(`child process exit with: code ${code}, signal: ${signal}`);
});

Общие параметры

{
    cwd: String,
    env: Object,
    stdio: Array | String,
    detached: Boolean,
    shell: Boolean,
    uid: Number,
    gid: Number
}

Сосредоточьтесь на отсоединенном атрибуте, для отсоединенного установлено значениеtrueЭто подготовка к независимому запуску дочернего процесса. Конкретное поведение дочернего процесса связано с операционной системой.Различные системы ведут себя по-разному.Дочерний процесс системы Windows будет иметь собственное окно консоли, а дочерний процесс системы POSIX станет новой группой процессов и лидером сеанса.

В это время дочерний процесс не является полностью независимым, результат выполнения дочернего процесса будет отображаться в потоке данных, заданном основным процессом, а выход основного процесса повлияет на работу дочернего процесса. когда для stdio установлено значениеignoreи позвониchild.unref();Дочерний процесс начинает выполняться действительно независимо, и основной процесс может завершиться независимо.

exec

exec(command[, options][, callback])

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

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

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

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

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

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

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

child.on('exit', (code, signal) => {
    console.log(`child process exit with: code ${code}, signal: ${signal}`);
});

execFile

child_process.execFile(file[, args][, options][, callback])

выполнить файл

В основном то же самое, что и функция exec, разница в том, что файл скрипта выполняется по заданному пути, и новый процесс создается напрямую, вместо создания среды оболочки для запуска скрипта, что относительно легко и эффективно. Но в системе Windows, например.cmd,.batЕсли файл нельзя запустить напрямую, execFile не будет работать, вместо этого вы можете использовать spawn и exec.

fork

child_process.fork(modulePath[, args][, options])

Выполнить файл Node.js

// parent.js

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

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

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

child.send({ hello: 'world' });
// child.js

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

let counter = 0;

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

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

Сценарии использования подпроцессов

  • Вычислительно интенсивная система
  • Передние инструменты строительства используют многоядерные параллельные вычисления ЦП для повышения эффективности строительства.
  • Инструменты управления процессами, такие как: некоторые функции в PM2

На практике: Akyuu PM запускает проект

  1. Процесс ①: используется для анализа пользовательского ввода и вызова команды запуска.
    const commander = require('commander');
    const path = require('path');
    const start = require('./lib/start');
    
    commander
        .command('start <entry>')
        .description('start process')
        .option('--dev', 'set dev environment')
        .action(function(entry, options) {
            start({
                entry: path.resolve(__dirname, `../entry/${entry}`),
                isDaemon: !options.dev
            });
        });
    
  2. Процесс ①:
    1. Используйте spawn, чтобы запустить указанный процесс файла записи ②, установите для параметра detached значениеtrue,перечислитьchild.unref();Заставить дочерние процессы работать независимо
      const { spawn } = require('child_process');
      
      const child = spawn(process.execPath, [ path.resolve(__dirname, 'cluster') ], {
          cwd: path.resolve(__dirname, '../../'),
          env: Object.assign({}, process.env, {
              izayoiCoffee: JSON.stringify({
                  configDir: config.akyuuConfigDir,
                  entry: options.entry
              })
          }),
          detached: true,
          stdio: 'ignore'
      });
      
      child.on('exit', function(code, signal) {
          console.error(`start process \`${path.basename(options.entry)}\` failed, ` +
              `code: ${code}, signal: ${signal}`);
          process.exit(1);
      });
      
      child.unref();
      
    2. Записать pid дочернего процесса в файл
      child
          .on('fork', function(worker) {
              try {
                  fs.writeFileSync(
                      'pid file path',
                      worker.process.pid,
                      { encoding: 'utf8' }
                  );
              } catch(err) {
                  console.error(
                      '[%s] [uncaughtException] [master: %d] \n%s',
                      moment().utcOffset(8).format('YYYY-MM-DD HH:mm:ss.SSS'),
                      process.pid,
                      err.stack
                  );
              }
          })
          .on('exit', function(worker, code, signal) {
              try {
                  fs.unlinkSync('pid file path');
              } catch(err) {
                  console.error(
                      '[%s] [uncaughtException] [master: %d] \n%s',
                      moment().utcOffset(8).format('YYYY-MM-DD HH:mm:ss.SSS'),
                      process.pid,
                      err.stack
                  );
              }
          });
      
  3. Процесс ②: если процессу ② также необходимо запустить собственный подпроцесс, после запуска подпроцесса отслеживайте его собственное событие выхода и активно закрывайте подпроцесс, чтобы предотвратить превращение подпроцесса в процесс операционной системы и его неуправляемость.
    const { fork } = require('child_process);
    
    const child = fork('some child process file');
    
    // 程序停止信号
    process.on('SIGHUP', function() {
        child.kill('SIGHUP');
        process.exit(0);
    });
    
    // kill 默认参数信号
    process.on('SIGTERM', function() {
        child.kill('SIGHUP');
        process.exit(0);
    });
    
    // Ctrl + c 信号
    process.on('SIGINT', function() {
        child.kill('SIGHUP');
        process.exit(0);
    });
    
    // 退出事件
    process.on('exit', function() {
        child.kill('SIGHUP');
        process.exit(0);
    });
    
    // 未捕获异常
    process.on('uncaughtException', function() {
        child.kill('SIGHUP');
        process.exit(0);
    });
    

Суммировать

В разработке Node.js, особенно в процессе разработки API, редко участвуют подпроцессы, но они все же являются важной частью. Node.js может использовать подпроцессы для выполнения некоторых ресурсоемких задач.Хотя он не так эффективен и удобен, как другие языки, такие как C, но все же является решением.Node.js можно использовать для поддержки бизнес-сценариев без освоения других языки. Добыча полезных ископаемых и использование подпроцессов описаны в этой статье для дальнейшего использования.