node.js clusterでHTTPサーバをマルチプロセス化する
node.jsのclusterを使用してHTTPサーバ(ウェブサーバ)を並列化し、パフォーマンスを上げられます。
読み方
- cluster
- くらすた
目次
概要
node.jsの問題点として、シングルプロセスで処理をしていて、あるリクエストの処理が長くかかる場合、ほかのすべてのリクエストをブロックする可能性があります。httpモジュールで、ウェブサーバを起動したとき、同時に処理できるアクセスは、1つだけなので、処理がつまると、後ろに並んでいるリクエストの待ち行列は、ブロックされた状態になります。
そのような問題を解決するために、並行サーバ(並列)、マルチスレッドモデルなどがあるわけです。 node.jsでは、この問題に対処するいくつかの方法がありますが、ここでは、clusterを用いた例を示します。
clusterは、マルチコアを活かすためのものです。並行サーバは、コア数以上は実際に動作しないようで、コアが合計2個しかなければ、コア数より多くfork()したとしても、2個分のプロセスしか仕事をしません。
clusterの考え方は、伝統的なUnixプログラミングのforkモデルと同じと捉えて差し支えありません。
cluster.js で示すコードでは、node で起動されたプログラム(親)は、コア数だけ fork を行い、子プロセスがHTTPサーバを起動し、リクエストを処理します。
Unixのfork()と異なるのは、Unixのforkは、forkを呼び出した直後のプログラムが親プロセスと子プロセスで実行されますが、node.jsのclusterの場合は、forkを実行すると、同じプログラムが最初から起動されます。
Clusterのサンプルプログラム
HTTPサーバをマルチプロセス化する前に、Clusterの簡単な例を示します。親プロセスかどうかの判断は、 cluster.isMaster を参照し、真であれば、親と判断します。 子プロセスは、 process.exit() で終了させることができます。
/* * cluster1.js * Copyright (C) 2014 kaoru <kaoru@bsd> */ var cluster = require('cluster'); if (cluster.isMaster) { console.log ('parents'); var worker = cluster.fork(); cluster.on('exit', function(worker, code, signal) { console.log('worker ' + worker.process.pid + ' died'); }); } else { console.log("worker"); process.exit(); }
実行例は、以下の通りです。
node cluster1.js parents worker worker 47429 died
子プロセスが何らかの理由で終了してしまった場合に、以下のコードが実行されます。
cluster.on('exit', function(worker, code, signal) { console.log('worker ' + worker.process.pid + ' died'); });
子プロセスが終了してしまった場合に、子プロセスを作り直すには、上記のコードに fork() を追加することで実現できます。
cluster.on('exit', function(worker, code, signal) { console.log('worker ' + worker.process.pid + ' died'); cluster.fork(); });
マルチプロセスのHTTPサーバでは、リクエストをさばくための子プロセスが終了したままになってしまうと、リクエストをさばく子プロセスが減ったままになってしまいます。また、何らかの理由で、すべての子プロセスが終了してしまった場合、リクエストがさばけなくなってしまいます。そのために、いなくなってしまった子プロセスの分のプロセスを再作成する必要が出てきます。
CPUの数を調べる方法
マルチプロセス化する時にCPUの数(コアの数)を調べて、fork()します。そのために、以下のコードでCPUの数を調べることができます。
var numCPUs = require('os').cpus().length;
ClusterでHTTPサーバをマルチプロセス化する例
cluster.js
/* * cluster.js * Copyright (C) 2014 kaoru <kaoru@bsd> */ var cluster = require('cluster'); var http = require('http'); var numCPUs = require('os').cpus().length; var port = 8080; var max_server = numCPUs; // max server if (cluster.isMaster) { console.log ('master'); for ( var i = 0 ; i < max_server ; ++i) { cluster.fork(); } cluster.on('exit', function(worker, code, signal) { console.log('worker ' + worker.process.pid + ' died'); cluster.fork(); }); } else { console.log("worker"); var server = http.createServer(function(req, res){ res.writeHead(200); res.end('Hello World'); }).listen(port); }
実行例
node cluster.js
ベンチマーク
node.jsによるHTTPサーバの作り方の簡単なHTTPサーバとcluster.jsを ab でベンチマークしました。 Hello World レベルのプログラムのベンチマークです。10000 リクエストを並列30でベンチマークした場合です。
ab -n 10000 -c 30 http://localhost:8080/
ベンチマークの結果は、平行サーバは、 2.3 秒、非平行サーバは 3.1秒と、ほんのすこしだけ、パフォーマンスが上がっていることが確認できました。
コア数 2 の並行サーバ
Document Path: / Document Length: 12 bytes Concurrency Level: 30 Time taken for tests: 2.323 seconds Complete requests: 10000 Failed requests: 0 Total transferred: 870000 bytes HTML transferred: 120000 bytes Requests per second: 4304.82 [#/sec] (mean) Time per request: 6.969 [ms] (mean) Time per request: 0.232 [ms] (mean, across all concurrent requests) Transfer rate: 365.74 [Kbytes/sec] received
非平行サーバ
Document Path: / Document Length: 12 bytes Concurrency Level: 30 Time taken for tests: 3.175 seconds Complete requests: 10000 Failed requests: 0 Total transferred: 1130000 bytes HTML transferred: 120000 bytes Requests per second: 3150.08 [#/sec] (mean) Time per request: 9.524 [ms] (mean) Time per request: 0.317 [ms] (mean, across all concurrent requests) Transfer rate: 347.62 [Kbytes/sec] received
関連項目
- node.jsによるHTTPサーバの作り方
- node.jsによるHTTPSサーバの作り方
- node.jsでSPDY対応ウェブサーバ
- node.js foreverでHTTPサーバをデーモン化する
- node.js HTTPサーバでApacheライクなログを記録する
- node.js HTTPサーバでレスポンスをgzipやdeflateで圧縮する
- node.js clusterでHTTPサーバをマルチプロセス化する
ツイート