アシアルブログ

アシアルの中の人が技術と想いのたけをつづるブログです

Node.jsスクリプトの複数プロセス化を試してみました

こんにちは、中川です。
今回もPHPには一切触れないで、node.jsについて書いていこうと思います。

node.jsといえば、サーバサイドのV8で動作するJavaScriptのため、
通常、シングルスレッドでの動作となります。
ただ、そのまま使ってしまうと、最近のマシンはマルチコアが当たり前なため、
1コアだけ使うようなプログラムではもったいないということがありますね。

そこで、node.jsでもスクリプトを複数プロセス立ち上げ、
マルチコア環境でも有効に利用できるようにしてくれるモジュールを色々試してみました。

■環境


Ubuntu10.10
Node v0.4.0
・spark2@2.0.11
・cluster@0.2.4
・fugue@0.1.1
・multi-node@0.2.2
※ポート: 3000番
※Worker数: 2個


■通常スクリプト(1プロセス)

単純にhttpでの接続を受け付け、Hello Worldを返すスクリプトです。


// server.default.js
var http = require('http');

var count = 0;
var server = http.createServer(function(req, res) {
  console.log(count++);
  res.writeHead(200);
  res.end("Hello World: " + count + "\n");
});

server.listen(3000);
console.log("Start server");

・実行方法


$ node server.default.js


■spark
https://github.com/senchalabs/spark

インストール後に利用できる、sparkコマンドにてスクリプトを実行する。


$ npm install spark

・実行方法


$ spark -n 2 -v server.spark.js


次項のspark2と同スクリプトにて確認。

■Spark2
https://github.com/davglass/spark2

次項のsparkから開発上の理由?からforkされたもの。
実行時にspark2コマンドにて、worker数などを指定。
スクリプトでは、module.exportsでserverを指定。



$ npm install spark2




// server.spark.js
var http = require('http');

var count = 0;

module.exports = http.createServer(function(req, res) {
  console.log(count++);
  res.writeHead(200);
  res.end("Hello World: " + count + "\n");
});
console.log("Start Server");

・実行方法


$ spark2 -n 2 -v server.spark2.js

※ -n: worker数、 -v: verbose

■cluster
https://github.com/LearnBoost/cluster

比較的新しいモジュール。スクリプト内でclusterを設定。
plugin的に拡張ができるっぽい?
スクリプト内で、cluster関数でworker数などを指定。



$ npm install cluster




// server.cluster.js
var http = require('http');
var cluster = require('cluster');

var count = 0;
var server = http.createServer(function(req, res) {
  console.log(count++);
  res.writeHead(200);
  res.end("Hello World: " + count + "\n");
});

cluster(server)
  .set('workers', 2)
  .listen(3000);
console.log("Start server");

・実行方法


$ node server.cluster.js


■fugue
https://github.com/pgte/fugue

Unicornライクなモジュールとのこと。
スクリプト内で、fugue.startメソッドを使ってポートや、worker数を指定。



$ npm install fugue




// server.fugue.js
var fugue = require('fugue');
var http = require('http');

var count = 0;
var server = http.createServer(function(req, res) {
  console.log(count++);
  res.writeHead(200);
  res.end("Hello World: " + count + "\n");
});

fugue.start(server, 3000, null, 2, {verbose : true});
console.log("Start server");

・実行方法


$ node server.fugue.js


■multi-node
https://github.com/kriszyp/multi-node

あまり更新されていない感じ。
スクリプト内で、multi-node.listenにてポートや、worker数を指定。
npmでも直接提供されていないようなので、git cloneしてからインストール。



$ git clone https://github.com/kriszyp/multi-node.git
$ npm install ./multi-node




// server.multi-node.js
var http = require('http');

var count = 0;
var server = http.createServer(function(req, res) {
  console.log(count++);
  res.writeHead(200);
  res.end("Hello World: " + count + "\n");
});

var nodes = require("multi-node").listen({
  port: 3000,
  nodes: 2
}, server);

console.log("Start server");

・実行方法


$ node server.multi-node.js


■custom-script
http://www.slideshare.net/the_undefined/nodejs-in-production?from=ss_embed

こちらは、モジュールではなく、自前で書いて複数プロセス化。
上記のスライドの最後の方にあります。



// server.custom-master.js
var netBinding = process.binding('net');
var spawn = require('child_process').spawn;
var net = require('net');

var serverSocket = netBinding.socket('tcp4');
netBinding.bind(serverSocket, 3000);
netBinding.listen(serverSocket, 128);

for (var i = 0; i < 2; i++) {
  var fds = netBinding.socketpair();
  var child = spawn(process.argv[0], [__dirname + '/server.custom-worker.js'], {
    customFds: [fds[0], 1, 2]
  });
  var socket = new net.Stream(fds[1], 'unix');
  socket.write('hi', 'utf8', serverSocket);
}




// server.custom-worker.js
var net = require('net');
var http = require('http');
var socket = new net.Socket(0, 'unix');

socket.on('fd', function(fd) {
  startServer(fd);
}).resume();

function startServer(fd) {
  var count = 0;
  http.createServer(function (req, res) {
    console.log(count++);
    res.writeHead(200);
    res.write("count: " + count + "\n");
    res.end('Hello from: ' + process.pid + "\n");
  }).listenFD(fd);
  console.log('Worker Ready: ' + process.pid);
}

・実行方法


$ node server.custom-master.js


■動作確認
今回は、別のマシンのVirtualBox上のイメージに2コアを割り当てて、
以下のように手元のMacBookから、上記のスクリプト毎にそれぞれ起動して、
ざっくりとApacheBenchで試してみました。



$ ab -n 10000 -c 100 http://server:3000/


※複数プロセスが起動しているかの確認は、
サーバ上で、psコマンドや、topコマンドにて。
(top起動後「1」を押すとcpu毎の数値が見れます。)

それぞれ、3回程度行いました。
※今回の計測方法ではデフォルト以外はあまり違いが出なかったので以下ざっくりと結果を。



デフォルト(1プロセス):800req/sec
その他   (2プロセス):1100req/sec


といった感じでした。

複数プロセス化もこのように色々なモジュールが出ていますので、
スクリプトの変更も最小限で簡単に対応することができそうですね。

ただし、安定性や、さらに多くのプロセスを起動した場合など、
また、それぞれ中身は一切確認しておらず、とりあえず動かしてみた程度なので、
今後も色々と調べていきたいと思います。