48

これが私がやろうとしていることです:私はNode.js httpサーバーを開発しています。これは、単一のマシンで何万ものモバイルクライアントからのプッシュ目的(redisとのコラボレーション)のために長い接続を保持します。

テスト環境:

1.80GHz*2 CPU/2GB RAM/Unbuntu12.04/Node.js 0.8.16

初めて、「express」モジュールを使用しました。これにより、スワップが使用される前に約 120k の同時接続に到達できました。つまり、RAM が十分ではありませんでした。次に、ネイティブの「http」モジュールに切り替えたところ、最大で約 160k の同時実行性が得られました。しかし、ネイティブ http モジュールにはまだ必要のない機能が多すぎることに気付いたので、ネイティブの "net" モジュールに切り替えました (これは、http プロトコルを自分で処理する必要があることを意味しますが、それで問題ありません)。今では、1 台のマシンあたり約 250,000 の同時接続に到達できます。

私のコードの主な構造は次のとおりです。

var net = require('net');
var redis = require('redis');

var pendingClients = {};

var redisClient = redis.createClient(26379, 'localhost');
redisClient.on('message', function (channel, message) {
    var client = pendingClients[channel];
    if (client) {
        client.res.write(message);
    }
});

var server = net.createServer(function (socket) {
    var buffer = '';
    socket.setEncoding('utf-8');
    socket.on('data', onData);

    function onData(chunk) {
        buffer += chunk;
        // Parse request data.
        // ...

        if ('I have got all I need') {
            socket.removeListener('data', onData);

            var req = {
                clientId: 'whatever'
            };
            var res = new ServerResponse(socket);
            server.emit('request', req, res);
        }  
    }
});

server.on('request', function (req, res) {
    if (res.socket.destroyed) {            
        return;
    }

    pendingClinets[req.clientId] = {
        res: res
    };

    redisClient.subscribe(req.clientId);

    res.socket.on('error', function (err) {
        console.log(err);
    });

    res.socket.on('close', function () {
        delete pendingClients[req.clientId];

        redisClient.unsubscribe(req.clientId);
    });
});

server.listen(3000);

function ServerResponse(socket) {
    this.socket = socket;
}
ServerResponse.prototype.write = function(data) {
    this.socket.write(data);
}

最後に、ここに私の質問があります:

  1. メモリ使用量を減らして同時実行性をさらに高めるにはどうすればよいですか?

  2. Node.js プロセスのメモリ使用量を計算する方法について、私は本当に混乱しています。Chrome V8 を搭載した Node.js を知っています。process.memoryUsage () API があり、rss/heapTotal/heapUsed の 3 つの値を返します。それらの違いは何ですか。どの部分をもっと気にする必要がありますか。 Node.jsプロセスによって使用されるメモリ?

  3. いくつかのテストを行ったにもかかわらず、メモリリークが心配で、問題はないようです。気になる点やアドバイスはありますか?

  4. V8 hidden classに関するドキュメントを見つけました。説明されているように、上記のコードのようにclientIdという名前のプロパティをグローバル オブジェクトpendingClientsに追加すると、新しい隠しクラスが生成されるということですか? それはメモリリークを引き起こしますか?

  5. Node.js プロセスのヒープ マップを分析するためにwebkit-devtools-agentを使用しました。プロセスを開始してヒープ スナップショットを作成し、10k のリクエストを送信して後で切断し、その後再びヒープ スナップショットを作成しました。比較の視点を使用して、これら 2 つのスナップショットの違いを確認しました。これが私が得たものです: ここに画像の説明を入力 誰かこれを説明できますか?(配列)/(コンパイルされたコード)/(文字列)/コマンド/配列の数とサイズが大幅に増加しましたが、これはどういう意味ですか?

編集:負荷テストはどのように実行しましたか?
1. まず、サーバー マシンとクライアント マシンの両方でいくつかのパラメータを変更しました(60k 以上の同時実行性を実現するには、複数のクライアント マシンが必要です。これは、1 台のマシンが最大 60k+ ポート (16 ビットで表される) しか持たないためです)。<br> 1.1. サーバーとクライアントの両方のマシンで、ファイル記述子を変更して、テスト プログラムが実行されるシェルでこれらのコマンドを使用します。

ulimit -Hn 999999
ulimit -Sn 999999

1.2. サーバー マシンでは、いくつかの net/tcp 関連のカーネル パラメータも変更しました。最も重要なものは次のとおりです。

net.ipv4.tcp_mem = 786432 1048576 26777216
net.ipv4.tcp_rmem = 4096 16384 33554432
net.ipv4.tcp_wmem = 4096 16384 33554432

1.3。クライアントマシンに関して:

net.ipv4.ip_local_port_range = 1024 65535

2. 次に、Node.js を使用してカスタム シミュレート クライアント プログラムを作成しました。ほとんどの負荷テスト ツール (ab、siege など) は短い接続用ですが、長い接続を使用しており、いくつかの特別な要件があるためです。
3. 次に、1 台のマシンでサーバー プログラムを起動し、別の 3 台のマシンで 3 つのクライアント プログラムを起動しました。

編集:1台のマシン(2GB RAM)で250kの同時接続に到達しましたが、あまり意味がなく実用的ではないことが判明しました。接続が接続されたとき、私は接続を保留にするだけなので、他には何もしません。それらに応答を送信しようとすると、同時実行数が約 150k に落ちました。私が計算したように、接続ごとに約 4KB のメモリ使用量があります。これは、 4096 16384 33554432に設定したnet.ipv4.tcp_wmemに関連していると思いますが、それを小さく変更しても何も変わりませんでした。理由がわかりません。

編集:実際には、tcp 接続ごとに使用されるメモリの量と、単一の接続で使用されるメモリの正確な構成にもっと興味がありますか? 私のテストデータによると:

150k の同時実行で約 1800M の RAM が消費され (無料の -m出力から)、Node.js プロセスには約 600M の RSS がありました。

次に、私はこれを仮定しました:

  • (1800M - 600M) / 150k = 8k、これは単一接続のカーネル TCP スタック メモリ使用量です。2 つの部分で構成されています: 読み取りバッファー (4KB) + 書き込みバッファー (4KB) (実際には、これは私の設定と一致しません)上記のnet.ipv4.tcp_rmemnet.ipv4.tcp_wmemについて、システムはこれらのバッファに使用するメモリの量をどのように決定しますか?)

  • 600M / 150k = 4k、これは単一接続の Node.js メモリ使用量です

私は正しいですか?両方の側面でメモリ使用量を減らすにはどうすればよいですか?

うまく説明できていないところがあれば教えてください。説明やアドバイスをいただければ幸いです。

4

2 に答える 2

5
  1. メモリ使用量をさらに減らすことについて心配する必要はないと思います。あなたが含めたその読み出しから、考えられる最小限にかなり近いようです(単位が指定されていない場合の標準であるバイト単位であると解釈します)。

  2. これは私が答えることができるよりも詳細な質問ですが、これがRSS . 私が最もよく理解しているように、ヒープは動的に割り当てられたメモリがUNIXシステムから来る場所です。したがって、ヒープの合計は、使用のためにヒープに割り当てられているすべてのように見えますが、使用されているヒープは、割り当てられているもののどれだけ使用したかです。

  3. メモリ使用量はかなり良好で、実際にリークしているようには見えません。まだ心配する必要はありません。=]

  4. わからない。

  5. このスナップショットは妥当なようです。要求の急増から作成されたオブジェクトの一部はガベージ コレクションされていて、他のオブジェクトはそうではなかったと思います。10,000 個を超えるオブジェクトはなく、これらのオブジェクトのほとんどは非常に小さいことがわかります。私はそれを良いと呼んでいます。

しかし、もっと重要なのは、これをどのように負荷テストしているのかということです。私は以前にこのような大規模な負荷テストを試みましたが、ほとんどのツールは Linux でその種の負荷を生成することができません。これは、開いているファイル記述子の数に制限があるためです (通常、デフォルトではプロセスごとに約 1,000 個)。 )。また、一度ソケットを使用すると、すぐに再び使用できるわけではありません。私が思い出したように、再び使用できるようになるまでにはかなりの数分かかります。これと、システム全体で開いているファイル記述子の制限が100k未満に設定されているのを通常見たという事実との間で、変更されていないボックスでそれほど多くの負荷を受け取ったり、単一のボックスでそれを生成したりできるかどうかはわかりません. そのような手順について言及していないので、負荷テストを調査して確認する必要があるかもしれないと思います。

于 2012-12-29T18:42:20.463 に答える
2

いくつかのメモ:

res をオブジェクト {res: res} でラップする必要がありますか?直接割り当てることはできますか?

pendingClinets[req.clientId] = res;

役立つかもしれない別の〜マイクロ最適化を編集

server.emit('request', req, res);

'request' に 2 つの引数を渡しますが、実際には要求ハンドラーが必要とするのは応答 'res' だけです。

res['clientId'] = 'whatever';
server.emit('request', res);

実際のデータの量は同じままですが、「リクエスト」ハンドラの引数リストの引数を 1 つ減らすと、参照ポインタ (数バイト) を節約できます。しかし、数十万の接続を処理している場合、数バイトが加算される可能性があります。また、emit 呼び出しで追加の引数を処理するためのマイナーな CPU オーバーヘッドも節約できます。

于 2013-01-03T20:57:51.217 に答える