ウェブサイト検索

クラスタリングを使用して Node.js アプリケーションをスケーリングする方法


著者は寄付プログラムへの書き込みを選択しました。

序章

複数の CPU を搭載したシステムで Node.js プログラムを実行すると、デフォルトで単一の CPU のみを使用して実行するプロセスが作成されます。 Node.js は単一のスレッドを使用して JavaScript コードを実行するため、アプリケーションへのすべての要求は、単一の CPU で実行されるそのスレッドによって処理される必要があります。アプリケーションに CPU 集中型のタスクがある場合、オペレーティング システムはタスクが完了するまで単一の CPU を共有するようにスケジュールする必要があります。その結果、1 つのプロセスが大量のリクエストを受け取ると、そのプロセスが過負荷になり、パフォーマンスが低下する可能性があります。プロセスがクラッシュすると、ユーザーはアプリケーションにアクセスできなくなります。

解決策として、Node.js はラウンドロビン アルゴリズムを導入しました。 1 つのインスタンスがクラッシュした場合でも、実行中の残りのプロセスによってユーザーにサービスを提供できます。負荷が複数のプロセス間で均等に共有され、単一のインスタンスが圧倒されるのを防ぐため、アプリケーションのパフォーマンスが大幅に向上します。

このチュートリアルでは、4 つ以上の CPU を搭載したマシンで cluster モジュールを使用して Node.js アプリケーションをスケーリングします。クラスタリングを使用しないアプリケーションを作成してから、クラスタリングを使用するようにアプリを変更します。 pm2 モジュールを使用して、アプリケーションを複数の CPU にスケーリングします。負荷テスト ツールを使用して、クラスタリングを使用するアプリと使用しないアプリのパフォーマンスを比較し、pm2 モジュールを評価します。

前提条件

このチュートリアルに従うには、次のものが必要です。

  • 4 つ以上の CPU を搭載したシステム。
    • Ubuntu 22.04 でリモート サーバーを使用している場合は、初期サーバー セットアップに従ってシステムをセットアップできます。

    ステップ 1 - プロジェクト ディレクトリの設定

    この手順では、プロジェクトのディレクトリを作成し、このチュートリアルで後でビルドするアプリケーションの依存関係をダウンロードします。ステップ 5 で。

    まず、ディレクトリを作成します。 cluster_demo または任意のディレクトリ名で呼び出すことができます。

    1. mkdir cluster_demo

    次に、ディレクトリに移動します。

    1. cd cluster_demo

    次に、プロジェクトを初期化します。これにより、package.json ファイルも作成されます。

    1. npm init -y

    -y オプションは、すべてのデフォルト オプションを受け入れるように NPM に指示します。

    コマンドを実行すると、次のような出力が生成されます。

    Output
    Wrote to /home/sammy/cluster_demo/package.json: { "name": "cluster_demo", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }

    特定のプロジェクトに合わせた次のプロパティに注意してください。

    • name: npm パッケージの名前
    • version: パッケージのバージョン番号。
    • main: プロジェクトのエントリ ポイント。

    他のプロパティの詳細については、NPM のドキュメントの package.json セクションを参照してください。

    次に、任意のエディターで package.json ファイルを開きます (このチュートリアルでは nano を使用します)。

    1. nano package.json

    package.json ファイルで、強調表示されたテキストを追加して、パッケージのインポート時に ES モジュールのサポートを有効にします。

    {
      ...
      "author": "",
      "license": "ISC",
      "type": "module"
    }
    

    CTRL+X でファイルを保存して閉じます。

    次に、次のパッケージをダウンロードします。

    • express: Node.js で Web アプリケーションを構築するためのフレームワーク
    • loadtest: アプリへのトラフィックを生成してパフォーマンスを測定するのに役立つ負荷テスト ツールです。
    • pm2: アプリを複数の CPU に自動的にスケーリングするツール。

    次のコマンドを実行して、Express パッケージをダウンロードします。

    1. npm install express

    次に、コマンドを実行して loadtest および pm2 パッケージをグローバルにダウンロードします。

    1. npm install -g loadtest pm2

    必要な依存関係をインストールしたので、クラスタリングを使用しないアプリケーションを作成します。

    ステップ 2 — クラスターを使用せずにアプリケーションを作成する

    この手順では、ユーザーがアクセスするたびに CPU を集中的に使用するタスクを開始する単一のルートを含むサンプル プログラムを作成します。このプログラムは cluster モジュールを使用しないため、1 つの CPU でアプリの 1 つのインスタンスを実行した場合のパフォーマンスへの影響にアクセスできます。チュートリアルの後半で、このアプローチを cluster モジュールのパフォーマンスと比較します。

    nano またはお気に入りのテキスト エディターを使用して、index.js ファイルを作成します。

    1. nano index.js

    index.js ファイルに次の行を追加して、Express をインポートしてインスタンス化します。

    import express from "express";
    
    const port = 3000;
    const app = express();
    
    console.log(`worker pid=${process.pid}`);
    

    最初の行で、express パッケージをインポートします。 2 行目で、port 変数を、アプリケーションのサーバーがリッスンするポート 3000 に設定します。次に、app 変数を Express のインスタンスに設定します。その後、組み込みの process モジュールを使用して、アプリケーションのプロセスのプロセス ID をコンソールに記録します。

    次に、これらの行を追加してルート /heavy を定義します。これには CPU バウンド ループが含まれます。

    ...
    app.get("/heavy", (req, res) => {
      let total = 0;
      for (let i = 0; i < 5_000_000; i++) {
        total++;
      }
      res.send(`The result of the CPU intensive task is ${total}\n`);
    });
    

    /heavy ルートでは、total 変数を 500 万回インクリメントするループを定義します。次に、res.send() メソッドを使用して、total 変数の値を含む応答を送信します。 CPU バウンド タスクの例は任意ですが、複雑さを増すことなく CPU バウンド タスクを示しています。ルートに他の名前を使用することもできますが、このチュートリアルでは /heavy を使用してパフォーマンスの高いタスクを示します。

    次に、Express モジュールの listen() メソッドを呼び出して、サーバーがポート 3000 をリッスンし、port 変数に格納します。

    ...
    app.listen(port, () => {
      console.log(`App listening on port ${port}`);
    });
    

    完全なファイルは次のようになります。

    import express from "express";
    
    const port = 3000;
    const app = express();
    
    console.log(`worker pid=${process.pid}`);
    
    app.get("/heavy", (req, res) => {
      let total = 0;
      for (let i = 0; i < 5_000_000; i++) {
        total++;
      }
      res.send(`The result of the CPU intensive task is ${total}\n`);
    });
    
    app.listen(port, () => {
      console.log(`App listening on port ${port}`);
    });
    

    コードの追加が完了したら、ファイルを保存して終了します。 node コマンドを使用してファイルを実行します。

    1. node index.js

    コマンドを実行すると、出力は次のようになります。

    Output
    worker pid=11023 App listening on port 3000

    出力には、実行中のプロセスのプロセス ID と、サーバーがポート 3000 でリッスンしていることを確認するメッセージが示されます。

    アプリケーションが動作しているかどうかをテストするには、別のターミナルを開いて次のコマンドを実行します。

    1. curl http://localhost:3000/heavy

    注: リモート サーバーでこのチュートリアルに従っている場合は、別のターミナルを開き、次のコマンドを入力します。

    1. ssh -L 3000:localhost:3000 your_non_root_user@your_server_ip

    接続したら、次のコマンドを入力して、curl を使用してアプリにリクエストを送信します。

    1. curl http://localhost:3000/heavy

    出力は次のようになります。

    Output
    The result of the CPU intensive task is 5000000

    出力は、CPU を集中的に使用する計算の結果を提供します。

    この時点で、CTRL+C でサーバーを停止できます。

    node コマンドで index.js ファイルを実行すると、オペレーティング システム (OS) によってプロセスが作成されます。プロセスは、実行中のプログラムに対してオペレーティング システムが作成する抽象化です。 OS はプログラムにメモリを割り当て、すべての OS プロセスを含むプロセス リストにエントリを作成します。そのエントリはプロセス ID です。

    次に、プログラムバイナリが特定され、プロセスに割り当てられたメモリにロードされます。そこから、実行を開始します。実行中、システム内の他のプロセスは認識されず、プロセスで発生したことは他のプロセスに影響しません。

    複数の CPU を備えたサーバー上で実行されている Node.js アプリケーションに対して単一のプロセスがあるため、すべての着信要求を受信して処理します。この図では、すべての着信要求が単一の CPU で実行されているプロセスに送信され、他の CPU はアイドル状態のままです。

    cluster モジュールを使用せずにアプリを作成したので、次に cluster モジュールを使用してアプリケーションをスケーリングし、複数の CPU を使用します。

    ステップ3-アプリケーションのクラスタリング

    このステップでは、cluster モジュールを追加して同じプログラムの複数のインスタンスを作成し、より多くの負荷を処理してパフォーマンスを向上させます。 cluster モジュールでプロセスを実行すると、マシンの各 CPU で複数のプロセスを実行できます。

    この図では、リクエストはプライマリ プロセスのロード バランサーを通過し、ラウンド ロビン アルゴリズムを使用してプロセス間でリクエストを分散します。

    cluster モジュールを追加します。ターミナルで、primary.js ファイルを作成します。

    1. nano primary.js

    primary.js ファイルに、次の行を追加して依存関係をインポートします。

    import cluster from "cluster";
    import os from "os";
    import { dirname } from "path";
    import { fileURLToPath } from "url";
    
    const __dirname = dirname(fileURLToPath(import.meta.url));
    

    最初の 2 行で、cluster および os モジュールをインポートします。次の 2 行では、dirnamefileURLToPath をインポートします。これを使用して、__dirname 変数値をディレクトリの絶対パスに設定します。 index.js ファイルが実行されています。 __dirname は ES モジュールの使用時には定義されておらず、デフォルトでは CommonJS モジュールでのみ定義されているため、これらのインポートが必要です。

    次に、index.js ファイルを参照する次のコードを追加します。

    ...
    const cpuCount = os.cpus().length;
    
    console.log(`The total number of CPUs is ${cpuCount}`);
    console.log(`Primary pid=${process.pid}`);
    cluster.setupPrimary({
      exec: __dirname + "/index.js",
    });
    

    まず、cpuCount 変数をマシンの CPU の数に設定します。これは 4 つ以上である必要があります。次に、コンソールに CPU の数を記録します。その後、すべてのリクエストを受信するプライマリ プロセスのプロセス ID をログに記録し、ロード バランサーを使用してそれらをワーカー プロセスに分散します。

    その後、各ワーカーで実行されるように、cluster モジュールの setupPrimary() メソッドを使用して index.js ファイルを参照します。プロセスが生成されました。

    次に、次のコードを追加してプロセスを作成します。

    ...
    for (let i = 0; i < cpuCount; i++) {
      cluster.fork();
    }
    cluster.on("exit", (worker, code, signal) => {
      console.log(`worker ${worker.process.pid} has been killed`);
      console.log("Starting another worker");
      cluster.fork();
    });
    

    ループは cpuCount の値と同じ回数反復し、各反復中に cluster モジュールの fork() メソッドを呼び出します。 cluster モジュールの on() メソッドを使用して exit イベントをアタッチし、プロセスが exit を発行したときにリッスンします。通常、これはプロセスが終了したときです。 exit イベントがトリガーされると、停止したワーカーのプロセス ID をログに記録し、fork() メソッドを呼び出して新しいワーカー プロセスを作成し、停止したワーカー プロセスを置き換えます。プロセス。

    完全なコードは次のようになります。

    import cluster from "cluster";
    import os from "os";
    import { dirname } from "path";
    import { fileURLToPath } from "url";
    
    const __dirname = dirname(fileURLToPath(import.meta.url));
    
    const cpuCount = os.cpus().length;
    
    console.log(`The total number of CPUs is ${cpuCount}`);
    console.log(`Primary pid=${process.pid}`);
    cluster.setupPrimary({
      exec: __dirname + "/index.js",
    });
    
    for (let i = 0; i < cpuCount; i++) {
      cluster.fork();
    }
    cluster.on("exit", (worker, code, signal) => {
      console.log(`worker ${worker.process.pid} has been killed`);
      console.log("Starting another worker");
      cluster.fork();
    });
    

    行の追加が完了したら、ファイルを保存して終了します。

    次に、ファイルを実行します。

    1. node primary.js

    出力は次のようになります (プロセス ID と情報の順序は異なる場合があります)。

    Output
    The total number of CPUs is 4 Primary pid=7341 worker pid=7353 worker pid=7354 worker pid=7360 App listening on port 3000 App listening on port 3000 App listening on port 3000 worker pid=7352 App listening on port 3000

    出力には、4 つの CPU、ロード バランサーを含む 1 つのプライマリ プロセス、およびポート 3000 でリッスンしている 4 つのワーカー プロセスが示されます。

    次に、2 番目のターミナルに戻り、/heavy ルートにリクエストを送信します。

    1. curl http://localhost:3000/heavy

    出力は、プログラムが機能していることを確認します。

    Output
    The result of the CPU intensive task is 5000000

    これでサーバーを停止できます。

    この時点で、マシンのすべての CPU で 4 つのプロセスが実行されます。

    アプリケーションにクラスタリングを追加すると、cluster モジュールを使用した場合と cluster モジュールを使用しない場合のプログラムのパフォーマンスを比較できます。

    ステップ 4 — 負荷テスト ツールを使用したパフォーマンスの比較

    このステップでは、loadtest パッケージを使用して、作成した 2 つのプログラムに対するトラフィックを生成します。 cluster モジュールを使用する primary.js プログラムのパフォーマンスと、使用しない index.js プログラムのパフォーマンスを比較します。クラスタリング。 cluster モジュールを使用するプログラムは、クラスタリングを使用しないプログラムよりも高速に実行され、特定の時間内により多くのリクエストを処理できることがわかります。

    まず、cluster モジュールを使用せず、単一のインスタンスでのみ実行される index.js ファイルのパフォーマンスを測定します。

    最初のターミナルで、index.js ファイルを実行してサーバーを起動します。

    1. node index.js

    アプリが実行されているという出力が表示されます。

    Output
    worker pid=7731 App listening on port 3000

    次に、2 番目のターミナルに戻り、loadtest パッケージを使用してサーバーにリクエストを送信します。

    1. loadtest -n 1200 -c 200 -k http://localhost:3000/heavy

    -n オプションは、パッケージが送信する必要があるリクエストの数を受け入れます。これは、ここでは 1200 リクエストです。 -c オプションは、サーバーに同時に送信する必要があるリクエストの数を受け入れます。

    リクエストが送信されると、パッケージは次のような出力を返します。

    Output
    Requests: 0 (0%), requests per second: 0, mean latency: 0 ms Requests: 430 (36%), requests per second: 87, mean latency: 1815.1 ms Requests: 879 (73%), requests per second: 90, mean latency: 2230.5 ms Target URL: http://localhost:3000/heavy Max requests: 1200 Concurrency level: 200 Agent: keepalive Completed requests: 1200 Total errors: 0 Total time: 13.712728601 s Requests per second: 88 Mean latency: 2085.1 ms Percentage of the requests served within a certain time 50% 2234 ms 90% 2340 ms 95% 2385 ms 99% 2406 ms 100% 2413 ms (longest request)

    この出力から、次のメトリックに注意してください。

    • Total time は、すべてのリクエストが処理されるまでにかかった時間を測定します。この出力では、1200 のすべてのリクエストを処理するのに 13 秒強かかりました。
    • 1 秒あたりのリクエスト数 は、サーバーが 1 秒あたりに処理できるリクエスト数を測定します。この出力では、サーバーは 1 秒あたり 88 のリクエストを処理しています。
    • 平均レイテンシ は、リクエストを送信してレスポンスを取得するまでにかかった時間を測定します。サンプル出力では、2085.1 ミリ秒 です。

    これらのメトリックは、ネットワークまたはプロセッサの速度によって異なりますが、これらの例に近いものになります。

    index.js ファイルのパフォーマンスを測定したので、サーバーを停止できます。

    次に、cluster モジュールを使用する primary.js ファイルのパフォーマンスを測定します。

    これを行うには、最初のターミナルに戻り、primary.js ファイルを再実行します。

    1. node primary.js

    以前と同じ情報を含む応答を受け取ります。

    Output
    The total number of CPUs is 4 Primary pid=7841 worker pid=7852 App listening on port 3000 worker pid=7854 App listening on port 3000 worker pid=7853 worker pid=7860 App listening on port 3000 App listening on port 3000

    2 番目のターミナルで、loadtest コマンドを再度実行します。

    1. loadtest -n 1200 -c 200 -k http://localhost:3000/heavy

    完了すると、同様の出力が表示されます (システムの CPU の数によって異なる場合があります)。

    Output
    Requests: 0 (0%), requests per second: 0, mean latency: 0 ms Target URL: http://localhost:3000/heavy Max requests: 1200 Concurrency level: 200 Agent: keepalive Completed requests: 1200 Total errors: 0 Total time: 3.412741962 s Requests per second: 352 Mean latency: 514.2 ms Percentage of the requests served within a certain time 50% 194 ms 90% 2521 ms 95% 2699 ms 99% 2710 ms 100% 2759 ms (longest request)

    cluster モジュールで実行されている primary.js アプリの出力は、合計時間が 13 秒から 3 秒に短縮されていることを示しています。 t クラスタリングを使用します。サーバーが 1 秒あたりに処理できるリクエストの数は、以前の 88 から 352 に 3 倍になりました。これは、サーバーに大きな負荷がかかる可能性があることを意味します。もう 1 つの重要な指標は平均レイテンシです。これは 2085.1 ms から 514.2 ms に大幅に低下しています。

    この応答により、スケーリングが機能し、アプリケーションが遅延なく短時間でより多くの要求を処理できることが確認されます。マシンをアップグレードして CPU を増やすと、アプリは自動的に CPU の数に合わせてスケーリングし、パフォーマンスをさらに向上させます。

    ネットワークとプロセッサの速度によって、ターミナル出力のメトリックが異なることに注意してください。合計時間と平均待ち時間が大幅に減少し、合計時間が急速に増加します。

    比較を行い、cluster モジュールを使用した方がアプリのパフォーマンスが向上することがわかったので、サーバーを停止できます。次のステップでは、cluster モジュールの代わりに pm2 を使用します。

    ステップ 5 — クラスタリングに pm2 を使用する

    これまで、cluster モジュールを使用して、マシンの CPU の数に応じてワーカー プロセスを作成しました。また、ワーカー プロセスが終了したときに再起動する機能も追加しました。このステップでは、cluster モジュールに基づいて構築された pm2 プロセス マネージャーを使用して、アプリのスケーリングを自動化するための代替手段を設定します。このプロセス マネージャーにはロード バランサーが含まれており、マシン上の CPU と同じ数のワーカー プロセスを自動的に作成できます。また、プロセスを監視したり、ワーカー プロセスが停止した場合に新しいワーカー プロセスを自動的に生成したりすることもできます。

    これを使用するには、スケールする必要があるファイル (このチュートリアルでは index.js ファイル) を含む pm2 パッケージを実行する必要があります。

    最初のターミナルで、次のコマンドを使用して pm2 クラスターを開始します。

    1. pm2 start index.js -i 0

    -i オプションは、pm2 で作成するワーカー プロセスの数を受け入れます。引数 0 を渡すと、pm2 はマシンの CPU と同じ数のワーカー プロセスを自動的に作成します。

    コマンドを実行すると、pm2 はワーカー プロセスの詳細を表示します。

    Output
    ... [PM2] Spawning PM2 daemon with pm2_home=/home/sammy/.pm2 [PM2] PM2 Successfully daemonized [PM2] Starting /home/sammy/cluster_demo/index.js in cluster_mode (0 instance) [PM2] Done. ┌─────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐ │ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │ ├─────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤ │ 0 │ index │ default │ 1.0.0 │ cluster │ 7932 │ 0s │ 0 │ online │ 0% │ 54.5mb │ nod… │ disabled │ │ 1 │ index │ default │ 1.0.0 │ cluster │ 7939 │ 0s │ 0 │ online │ 0% │ 50.9mb │ nod… │ disabled │ │ 2 │ index │ default │ 1.0.0 │ cluster │ 7946 │ 0s │ 0 │ online │ 0% │ 51.3mb │ nod… │ disabled │ │ 3 │ index │ default │ 1.0.0 │ cluster │ 7953 │ 0s │ 0 │ online │ 0% │ 47.4mb │ nod… │ disabled │ └─────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘

    テーブルには、各ワーカーのプロセス ID、ステータス、CPU 使用率、およびメモリ消費量が含まれており、プロセスの動作を理解するために使用できます。

    pm2 でクラスターを開始すると、パッケージはバックグラウンドで実行され、システムを再起動しても自動的に再起動します。

    ワーカー プロセスからログを読み取る場合は、次のコマンドを使用できます。

    1. pm2 logs

    ログの出力を受け取ります。

    Output
    [TAILING] Tailing last 15 lines for [all] processes (change the value with --lines option) /home/sammy/.pm2/pm2.log last 15 lines: ... PM2 | 2022-12-25T17:48:37: PM2 log: App [index:3] starting in -cluster mode- PM2 | 2022-12-25T17:48:37: PM2 log: App [index:3] online /home/sammy/.pm2/logs/index-error.log last 15 lines: /home/sammy/.pm2/logs/index-out.log last 15 lines: 0|index | worker pid=7932 0|index | App listening on port 3000 0|index | worker pid=7953 0|index | App listening on port 3000 0|index | worker pid=7946 0|index | worker pid=7939 0|index | App listening on port 3000 0|index | App listening on port 3000

    最後の 8 行で、ログはプロセス ID とポート番号 3000 を含む、実行中の 4 つのワーカー プロセスのそれぞれからの出力を提供します。この出力は、すべてのプロセスが実行中であることを確認します。

    次のコマンドを使用して、プロセスのステータスを確認することもできます。

    1. pm2 ls

    出力は次の表と一致します。

    Output
    ┌─────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐ │ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │ ├─────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤ │ 0 │ index │ default │ 1.0.0 │ cluster │ 7932 │ 5m │ 0 │ online │ 0% │ 56.6mb │ nod… │ disabled │ │ 1 │ index │ default │ 1.0.0 │ cluster │ 7939 │ 5m │ 0 │ online │ 0% │ 55.7mb │ nod… │ disabled │ │ 2 │ index │ default │ 1.0.0 │ cluster │ 7946 │ 5m │ 0 │ online │ 0% │ 56.5mb │ nod… │ disabled │ │ 3 │ index │ default │ 1.0.0 │ cluster │ 7953 │ 5m │ 0 │ online │ 0% │ 55.9mb │ nod… │ disabled │ └────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘

    クラスターが実行されているので、同じターミナルで次のコマンドを入力して、そのパフォーマンスをテストします。

    1. loadtest -n 1200 -c 200 -k http://localhost:3000/heavy

    出力は次のものとほぼ一致します。

    Output
    Requests: 0 (0%), requests per second: 0, mean latency: 0 ms Target URL: http://localhost:3000/heavy Max requests: 1200 Concurrency level: 200 Agent: keepalive Completed requests: 1200 Total errors: 0 Total time: 3.771868785 s Requests per second: 318 Mean latency: 574.4 ms Percentage of the requests served within a certain time 50% 216 ms 90% 2859 ms 95% 3016 ms 99% 3028 ms 100% 3084 ms (longest request)

    Total timeRequests per second、および Mean latency は、cluster を使用したときに生成された指標に近くなっていますモジュール。この配置は、pm2 スケーリングが同様に機能することを示しています。

    pm2 でワークフローを改善するために、構成ファイルを生成してアプリケーションの構成設定を渡すことができます。このアプローチにより、オプションを渡さずにクラスターを開始または再起動できます。

    構成ファイルを使用するには、現在のクラスターを削除します。

    1. pm2 delete index.js

    なくなったという応答を受け取ります。

    Output
    [PM2] Applying action deleteProcessId on app [index.js](ids: [ 0, 1, 2, 3 ]) [PM2] [index](2) ✓ [PM2] [index](1) ✓ [PM2] [index](0) ✓ [PM2] [index](3) ✓ ┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐ │ id │ name │ mode │ ↺ │ status │ cpu │ memory │ └────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘

    次に、構成ファイルを生成します。

    1. pm2 ecosystem

    出力は、ファイルが生成されたことを確認します。

    Output
    File /home/sammy/cluster_demo/ecosystem.config.js generated

    .js の名前を .cjs に変更して、ES モジュールのサポートを有効にします。

    1. mv ecosystem.config.js ecosystem.config.cjs

    エディターを使用して、構成ファイルを開きます。

    1. nano ecosystem.config.cjs

    ecosystem.config.cjs ファイルに、以下の強調表示されたコードを追加します。

    module.exports = {
      apps : [{
        script: 'index.js',
        watch: '.',
        name: "cluster_app",
        instances: 0,
        exec_mode: "cluster",
      }, {
        script: './service-worker/',
        watch: ['./service-worker']
      }],
    
      deploy : {
        production : {
          user : 'SSH_USERNAME',
          host : 'SSH_HOSTMACHINE',
          ref  : 'origin/master',
          repo : 'GIT_REPOSITORY',
          path : 'DESTINATION_PATH',
          'pre-deploy-local': '',
          'post-deploy' : 'npm install && pm2 reload ecosystem.config.cjs --env production',
          'pre-setup': ''
        }
      }
    };
    

    script オプションは、pm2 パッケージが生成する各プロセスで実行されるファイルを受け入れます。 name プロパティは、クラスターを識別できる任意の名前を受け入れます。これは、停止、再起動、またはその他のアクションを実行する必要がある場合に役立ちます。 instances プロパティは、必要なインスタンスの数を受け入れます。 instances0 に設定すると、pm2 は CPU と同じ数のプロセスを生成します。 exec_modecluster オプションを受け入れます。これは pm2 をクラスターで実行するように指示します。

    終了したら、ファイルを保存して閉じます。

    クラスターを開始するには、次のコマンドを実行します。

    1. pm2 start ecosystem.config.cjs

    次の応答が返されます。

    Output
    [PM2][WARN] Applications cluster_app, service-worker not running, starting... [PM2][ERROR] Error: Script not found: /home/node-user/cluster_demo/service-worker [PM2] App [cluster_app] launched (4 instances)

    最後の行は、4 個のプロセスが実行されていることを確認します。このチュートリアルでは service-worker スクリプトを作成していないため、service-worker が見つからないというエラーは無視できます。

    クラスタが動作していることを確認するには、ステータスを確認します。

    1. pm2 ls

    4 つのプロセスが実行されていることを確認する応答を受け取ります。

    Output
    ┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐ │ id │ name │ mode │ ↺ │ status │ cpu │ memory │ ├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤ │ 0 │ cluster_app │ cluster │ 0 │ online │ 0% │ 56.9mb │ │ 1 │ cluster_app │ cluster │ 0 │ online │ 0% │ 57.6mb │ │ 2 │ cluster_app │ cluster │ 0 │ online │ 0% │ 55.9mb │ │ 3 │ cluster_app │ cluster │ 0 │ online │ 0% │ 55.9mb │ └────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘

    クラスターを再起動する場合は、ecosystem.config.cjs ファイルで定義したアプリ名を使用できます。この場合は cluster_appです:

    1. pm2 restart cluster_app

    クラスターが再起動します。

    Output
    Use --update-env to update environment variables [PM2] Applying action restartProcessId on app [cluster_app](ids: [ 0, 1, 2, 3 ]) [PM2] [cluster_app](0) ✓ [PM2] [cluster_app](1) ✓ [PM2] [cluster_app](2) ✓ [PM2] [cluster_app](3) ✓ ┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐ │ id │ name │ mode │ ↺ │ status │ cpu │ memory │ ├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤ │ 0 │ cluster_app │ cluster │ 1 │ online │ 0% │ 48.0mb │ │ 1 │ cluster_app │ cluster │ 1 │ online │ 0% │ 47.9mb │ │ 2 │ cluster_app │ cluster │ 1 │ online │ 0% │ 38.8mb │ │ 3 │ cluster_app │ cluster │ 1 │ online │ 0% │ 31.5mb │ └────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘

    クラスターの管理を続行するには、次のコマンドを実行できます。

    Command Description
    pm2 start app_name Starts the cluster
    pm2 restart app_name Kills the cluster and starts it again
    pm2 reload app_name Restarts the cluster without downtime
    pm2 stop app_name Stops the cluster
    pm2 delete app_name Deletes the cluster

    pm2 モジュールと cluster モジュールを使用してアプリケーションをスケーリングできるようになりました。

    結論

    このチュートリアルでは、cluster モジュールを使用してアプリケーションをスケーリングしました。まず、cluster モジュールを使用しないプログラムを作成しました。次に、cluster モジュールを使用してアプリをマシン上の複数の CPU にスケーリングするプログラムを作成しました。その後、cluster モジュールを使用するアプリと使用しないアプリのパフォーマンスを比較しました。最後に、cluster モジュールの代わりに pm2 パッケージを使用して、アプリを複数の CPU にスケーリングしました。

    さらに進むには、cluster モジュールのドキュメント ページにアクセスして、モジュールの詳細を確認してください。

    pm2 を引き続き使用する場合は、Ubuntu 22.04 で本番用に Node.js アプリケーションをセットアップする方法を参照してください。

    Node.js には、Node.js と BullMQ で非同期タスクを処理する方法も同梱されています。