Node.jsは、1つのスレッドで実行されるのでマルチコアのCPUでは、複数のNode.jsを起動しないと効率的に処理されない。Clusterを使うと簡単に複数プロセス実行して分散処理できるようになる。[Node.js][TypeScript] Webサーバーを作り直すw(1)で作ったWebサーバをCluster対応してみる。

こんな、感じになる。

import * as http from "http";
import * as cluster from "cluster";	// (1)
import * as os from "os";

// コア数取得
const numCPUs = os.cpus().length;

if(cluster.isMaster) {	// マスターなら
	// CPUコア数のワーカーを起動 (2)
	for(let i = 0; i < numCPUs; i++) {
		cluster.fork();
	}
	cluster.on("exit", (worker, code, signal)=>{
		console.log(`worker ${worker.process.pid} died`);
	});
} else {
	let server = http.createServer((req, res):void => {
		console.log(`Worker pid:${process.pid}`);
		res.writeHead(200, { "Content-Type": "text/plain" });
		res.write(`Hello World!! (${process.pid})\n`);
		res.end();
	});
	server.listen(8080);
	console.log(`Worker ${process.pid} started`);
}

(1) クラスタモジュールをインポートする。
起動するとそのプロセスはマスタとなるので、isMasterはtrueが返る・
(2) CPUコア数分forkする。
すると、別プロセスでこのコードが頭から実行される。そのプロセスでは、isMasterはfalseを返すので、ポート番号8080でhttp接続待ちする。
これでWebブラウザなどから接続すると"Hello World!!"とプロセスIdが表示される。実行したNode.jsのコンソールにも応答したワーカーのプロセスIdが表示される。

いろいろ気になるところがあるんでつらつら書いていくと、

  • 複数プロセスで同じポート待ちすると普通エラーになるんだか、Clusterモジュール内でうまいことやっているみたいだ。
  • Webブラウザでアクセスしてリロードを繰り返しても同じプロセスばっかり応答する。
    これは、TCP接続ごとに割り振られていて、http1.1でTCP接続を維持するようになっているからだ。macOSやLinuxでwetを使ってアクセスすると応答するプロセスidは変わる。(Winowsの場合は変わらない場合がある)
  • Windowsの場合、プロセスidがかわらない。
    macOSやLinuxでは、プロセスの割り振りにデフォルトでラウンドロビン方式をとっていて、WindowsではOSにまかしているようで、たまたま(なのかな?)同じプロセスが応答しているようだ。ドキュメントによると使用しているライブラリ(libuv)でWinowsだと効率的に処理できないらしくて、OSにまかせる方法をデフォルトにしているらしい。効率的に処理できるようになるとデフォルトがmacOSやLinuxと同じラウンドロビン方式に変更されるようだ。
    あと、応答を返すところでループして応答を返すのを遅らせるようにしてwgetでアクセスすると別のプロセスが応答するようすがコンソールに表示されるのでclusterがちゃんと機能しているのは確認できる。