cluster
モジュールは,HTTP を含めた TCP 接続を複数の子プロセス (ワーカプロセス) で処理することにより,特にマルチコア環境でのスループット (リクエスト/秒) を向上するための機能です.cluster
モジュールによって API が提供されるようになり,その翌週の v0.6.0 リリース数時間前にはその API が変更されるというドタバタぶりでしたwソケットの基本
listening = socket(...); // ソケットをオープン bind(listening, ...); // ローカルアドレス&ポートを割り当て listen(listening, ...); // リスニングソケットに変換 for (;;) { connected = accept(listening, ...); // 接続済みソケットを返す ... close(connected); }
socket()
, bind()
, listen()
でリスニングソケットを準備して,accept()
でリスニングソケットに届いた接続済みソケットを取得します.accept()
を select()
等のシステムコールによって「多重化」するイベントループを使うためにこの問題はありませんが,しばらくの間それは忘れましょう.フォーク型
fork()
して新しいプロセスを作成するというものです.listening = socket(...); // ソケットをオープン bind(listening, ...); // ローカルアドレス&ポートを割り当て listen(listening, ...); // リスニングソケットに変換 for (;;) { connected = accept(listening, ...); // 接続済みソケットを返す if (fork() == 0) { // 子プロセス (親プロセスとは並行に実行される) ... exit(0); } // 親プロセス close(connected); }
子プロセスはソケット (ファイル記述子) を親プロセスから引き継ぎますから,接続済みソケットを使ってクライアントと送受信することができます.接続済みソケットを標準入出力に設定して
exec()
(現在のプロセス上で別のプログラムを実行) すれば,CGI の動作になります.ゲートウェイ型
// マスタ listening = socket(...); // ソケットをオープン bind(listening, ...); // ローカルアドレス&ポートを割り当て listen(listening, ...); // リスニングソケットに変換 for (;;) { connected = accept(listening, ...); // 接続済みソケットを返す worker = socket(...); // ワーカとの通信用のソケットをオープン connect(worker, ...); // ワーカに接続 ... // クライアントとワーカの間でデータを転送 close(worker); close(connected); }
accept()
もワーカとの接続や送受信も,全て多重化されてイベントループの中で行われるということにしてください.connect()
で接続するワーカをラウンドロビンなどの方法で切り替えることにより,複数のワーカにクライアントからの処理を分散することができます.新しいクライアントをどのワーカで処理させるか (ロードバランシング) はマスタが判断することになります.接続済みソケット共有型
// マスタ listening = socket(...); // ソケットをオープン bind(listening, ...); // ローカルアドレス&ポートを割り当て listen(listening, ...); // リスニングソケットに変換 for (;;) { connected = accept(listening, ...); // 接続済みソケットを返す ... // connected をワーカに転送する close(connected); }
Node.js の cluster モジュールは、v0.11.2 から Unix 系 (非 Windows) プラットフォームではこの方式がデフォルトになりました。マスタプロセスはラウンドロビン方式でワーカプロセスに接続済みソケットを渡します。明示的に「接続済みソケット共有型」を利用するには、
cluster.schedulingPolicy
に cluster.SCHED_RR
を指定します (または、NODE_CLUSTER_SCHED_POLICY
環境変数に rr
を指定します)。リスニングソケット共有型
// マスタ listening = socket(...); // ソケットをオープン bind(listening, ...); // ローカルアドレス&ポートを割り当て ... // ソケットをワーカに渡す
listen()
~accept()
はマスタで実行していましたが,この方式ではワーカがそれを実行します.// ワーカ ... // マスタからソケットを受け取る listen(listening, ...); // ソケットをリスニングソケットに変換 for (;;) { connected = accept(listening, ...); // 接続済みソケットを返す ... close(connected); }
この方法では一部のワーカプロセスに接続が偏りやすいため、v0.11.2 から Unix 系 (非 Windows) プラットフォームでは前述の「接続済みソケット共有型」がデフォルトになりました。v0.11.2 以降で「リスニングソケット共有型」を利用するには、
cluster.schedulingPolicy
に cluster.SCHED_NONE
を指定します (または、NODE_CLUSTER_SCHED_POLICY
環境変数に none
を指定します)。Node.js cluster モジュールの実際
cluster
モジュールを使用しない単純な HTTP サーバの例です.var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }).listen(1337, 'localhost');
cluster
モジュールを使わず直接実行すると,http.Server
(実際には net.Server
) の listen()
メソッドは,内部的にソケットの socket()
と bind()
, listen()
と accept()
を呼び出します.cluster
モジュールを使用すると,var cluster = require('cluster'); var http = require('http'); var numCPUs = require('os').cpus().length; if (cluster.isMaster) { // マスタ for (var i = 0; i < numCPUs; i++) { cluster.fork(); // ワーカを起動 } cluster.on('death', function(worker) { console.log('worker ' + worker.pid + ' died'); }); } else { // ワーカ http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }).listen(1337, 'localhost'); }
cluster
を使わない例と全く同じですが,http.Server
の listen()
メソッドの動作が変わります.ワーカとして実行されているプロセスでは,http.Server
の listen()
メソッドはマスタに「"localhost"
に 1337
番でバインドされたソケットをよこせ」という要求 (プロセス間通信) を行います.そこでマスタは socket()
, bind()
を呼び出してソケットを作成し,ワーカに転送します.ワーカはそのソケットに対して listen()
, accept()
を実行します."localhost"
に 1337
番でバインドされたソケットをよこせ」とマスタに要求すると,マスタは先ほど作成したソケットを転送します.これにより,複数のワーカ間でリスニングソケットが共有されます.cluster
モジュールを使ってみてください.