Skip to content

Использование нескольких ядер/процессоров в Node.js: порождаем воркеры

21/06/2010

Рабочие на фабрике в КитаеОдно из слабых мест Node.js — однопоточное выполнение кода. С этой проблемой можно бороться по разному: например, ставя load balancer’ы и запуская несколько экземпляров сервера. Но с версией 0.1.98 появился и новый способ: передача файлового дескриптора.

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

Я не UNIX-программист, поэтому за неточности сразу извините🙂

Каждому открытому файлу соответствует файловый дескриптор. Изначально дескриптор принадлежит тому процессу, который открыл файл, и если другой процесс хочет этим же файлом воспользоваться, он открывает его заново. Но в Unix есть возможность с помощью специального сигнала передать файловый дескриптор от одного процесса другому.

Когда Node открывает определённый порт, процесс получает такой же дескриптор, но не указывающий на файл на диске. Так просто его нельзя открыть второй раз — надо получить дескриптор от другого процессора. И если несколько экземпляров Node одновременно повесят http-серверы на этот дескриптор, получится забавная вещь: входящие запросы будут передаваться первому попавшемуся процессу из участвующих, он же будет возвращать ответ. Распределением запросов между процессами будет заниматься сама ОС — получится такой load balancer. Но дополнительно процесс-родитель сможет общаться с процессами-воркерами через их стандартный ввод-вывод: сохранять логи с stderr, получать и передавать сообщения, перезапускать потомков если что то у них пойдёт не так.

Нужные функции для передачи дескрипторов появились в Node.js только с версии 0.1.98: более ранние работать не будут. Сейчас мы попробуем собрать такую конструкцию из основного скрипта и нескольких экземпляров воркера.

Код

Теперь будем писать код🙂 Наши процессы будут просто возвращать «Hello, world», а их stdout и stderr будут выводиться в консоли главного процесса с пометкой какому из воркеров они принадлежат. Итак, поехали. Сначала создаём дескриптор:

var netBinding = process.binding('net');
// Создаём дескриптор для передачи
var fd = netBinding.socket('tcp4');
// Слушаем на порту 8080
netBinding.bind(fd, 8080);
netBinding.listen(fd, 128);

Теперь порождаем кучу воркеров и передаём им дескриптор с первым сообщением:

  for (var i = 0; i < workers; i++) {
    // Создаём пару сокетов
    var fds = netBinding.socketpair();
    var instance = i;2

    // Запускаем дочерний процесс
    var child = child_process.spawn(
      process.argv[0],
      ['worker.js', '--child'],
      undefined,
      [fds[1], -1, -1]
    );

    // Патч для пропадающего почему то stdin'а у дочернего процесса
    if (!child.stdin) {
      child.stdin = new net.Stream(fds[0], 'unix');
    }

    // Передаём потомку environment и дескриптор порта
    child.stdin.write(JSON.stringify({}), 'ascii', fd);
    sys.puts('Worker ' + i + ' started');
}

Воркер (файл worker.js в нашем случае) выглядит почти как обычный http-сервер, но вешается не на порт, а на дескриптор, полученный от процесса-родителя. В Node.js v0.1.98 для этого добавили метод listenFD():

var http = require('http'),
  sys = require('sys'),
  net = require('net');

// открываем stdin
var stdin = new net.Stream(0, 'unix');

// Получаем переменные окружения
stdin.addListener('data', function(json) {
  env = JSON.parse(json.toString());
});

// Получаем наш файловый дескриптор
stdin.addListener('fd', function(fd) {
  var app = http.createServer(function (req, res) {
    sys.puts('Request comes');
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World\n');
  });
  app.listenFD(fd, 'tcp4');
  sys.puts('Child listening');
});

stdin.resume();

Готово. Надо ещё добавить вывод сообщений об ошибках воркеров, чтобы проще было их отлаживать (я это сделал с помощью sys.puts()). Теперь всю конструкцию можно запустить с помощью основного скрипта.

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

Ссылки по теме

2 комментария
  1. Жора permalink

    Спасибо огромное за пример!
    Как раз пишу проект на nodeJS, частью которого есть написание веб-сервера. Так как я фактически до этого не писал на nodeJS мне очень не хватало именно этой информации.

Trackbacks & Pingbacks

  1. Использование нескольких ядер/процессоров в Node.js: порождаем воркеры « nodeJS

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: