Node.jsにおけるCPUを有効活用するクラスタリング

  • LINEで送る

Node.jsはシングルプロセスがシングルスレッドで実行されます.基本的にスレッドは一つのCPUで動作します.もしサーバーに4コアCPUが備わっている場合,シングルスレッドでは3つのCPUコアが使われないことになってしまいます.これはCPUを有効活用できていなく,多数のリクエストが来た場合でもCPUをフル活用できません.本記事はNode.jsでマルチプロセス化する方法を解説していきます.

クラスタリングによるマルチプロセス化

CPUを有効活用するためにNode.jsでは,クラスタリングと呼ばれる方法でCPUコアを無駄なく活用することができます.こちらのコードを見て下さい.

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  console.log(`Master ${process.pid} is running`);

  // Fork workers.
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`worker ${worker.process.pid} died`);
  });
} else {
  // Workers can share any TCP connection
  // In this case it is an HTTP server
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('hello world\n');
  }).listen(8000);

  console.log(`Worker ${process.pid} started`);
}
$ node server.js
Master 3596 is running
Worker 4324 started
Worker 4520 started
Worker 6056 started
Worker 5644 started

このコードはプロセスのIDをターミナルに出力します.まずはマスタープロセスがfor文でCPUの数だけ子プロセスを生成しています.ここで生成した子プロセスのことをworkerと呼びます.

cluster.fork();

ここの行が実際にworkerを生成している部分になります.またclusterにexitイベントを設定して,workerが消去された時,ターミナルにworkerIDと消去したメッセージを出力するようにしています.

ここで注目して欲しいのが,マスタープロセスはworkerを生成しているだけで,実際にサーバーを生成しているのは各々のworkerになっています.

最後,生成したworkerのサーバーをスタートさせたことをworkerIDと一緒に出力しています.

この方法を使えばマルチプロセス化でき,CPUコアの数だけworkerを生成することで,CPUコアを有効活用することができます.

workerの「schedulingPolicy」による負荷分散

cluster.fork()を使えば簡単にworkerが生成できることがわかりましたが,そのworkerにはどのようにアクセスが振り分けられるのでしょうか.それは「cluster.schedulingPolicy」で設定することができます.以下のコードを見てください.

const cluster=require('cluster');
cluster.schedulingPolicy = cluster.SCHED_RR;

「cluster.schedulingPolicy」の設定はグローバル設定です.設定できる値は2つあります.

  • cluster.SCHED_RR
  • cluster.SCHED_NONE

「cluster.SCHED_RR」はラウンドロビンです.ラウンドロビンとはアクセスを各workerに均等に振り分けることです.「cluster.SCHED_NONE」はOSに任せる設定となります.デフォルトでは「cluster.SCHED_RR」が設定されています.

workerの消滅に伴う自動再起動

workerをCPUコアの数だけ生成し,負荷分散もできるようになりました.しかし,あるworkerが異常終了(killと呼ぶ)した場合,代わりとなるworkerが生成されません.そうなるとCPUコアを有効活用できずに処理が進んでしまいます.これを防ぐために,workerに終了イベントを設定し,workerが異常終了した場合に「cluster.fork()」でworkerを補填します.

以下のコードを見てください.先程のクラスタリングのコードに負荷分散と自動再起動を組み込んだコードになります.

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
cluster.schedulingPolicy = cluster.SCHED_RR;

if (cluster.isMaster) {
  console.log(`Master ${process.pid} is running`);

  // Fork workers.
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log('worker %d died (%s). restarting...', 
                 worker.process.pid, signal || code);
    //workerが異常終了した場合にworkerを補填する
    if ( code !== 0 && !worker.exitedAfterDisconnect){
       cluster.fork();
    }
  });
} else {
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('hello world\n');
  }).listen(8000);

  console.log(`Worker ${process.pid} started`);
}

clusterの終了イベントのコールバック関数のcodeには異常終了した場合,0以外の数字が入ります.「worker.exitedAfterDisconnect」は異常終了(.kill() またはdisconnect())した場合,trueを返します.このプロパティで自発的終了か偶発的終了かを判断します.偶発的終了と判断した場合のみ,workerを再生成します.

実際にコードを実行し,別のターミナルで以下のコマンドを実行してworkerをkillしてみましょう.

kill 任意のworkerID

workerが異常終了した場合しっかりと新しいworkerが生成されています.これでNode.jsにおけるCPUを有効活用するクラスタリングの解説は終了です.

この記事は以下のサイトを参考にしています.

Node.js v14.5.0 Documentation Cluster

https://nodejs.org/api/cluster.html#cluster_event_exit_1

SNSでもご購読できます。

コメント

コメントを残す

*