これが私がやろうとしていることです:Node.js httpサーバーを開発しています。これは、1台のマシンで数万のモバイルクライアントからの目的(redisとのコラボレーション)をプッシュするために長い接続を保持します。
テスト環境:
1.80GHz*2 CPU/2GB RAM/Unbuntu12.04/Node.js 0.8.16
初めて、「エクスプレス」モジュールを使用しました。これにより、スワップが使用される前に約120kの同時接続に到達する可能性があり、RAMでは不十分です。その後、ネイティブの「http "モジュール、最大160kまでの同時実行性を取得しました。しかし、ネイティブhttpモジュールには不要な機能がまだ多すぎることに気付いたので、ネイティブ" net "モジュールに切り替えました(つまり、httpを処理する必要があります)自分でプロトコルを作成しますが、問題はありません)今では、1台のマシンあたり約25万の同時接続に到達できます。
これが私のコードの主な構造です:
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);
}
最後に、これが私の質問です:
メモリ使用量を減らして同時実行性をさらに高めるにはどうすればよいですか?
Node.jsプロセスのメモリ使用量を計算する方法について本当に混乱しています。 Chrome V8を搭載したNode.jsを知っています。process.memoryUsage() apiがあり、3つの値を返します:rss/heapTotal/heapUsed、違いは何ですか?それら、どの部分をもっと気にする必要がありますか?Node.jsプロセスで使用されるメモリの正確な構成は何ですか?
いくつかのテストを実行しましたが、問題はないようですが、メモリリークを心配しました。気になる点やアドバイスはありますか?
V8 hidden class についてのドキュメントを見つけたので、説明したように、-clientIdという名前のプロパティをグローバルオブジェクトに追加すると、 pendingClients上記のコードと同じように、新しい隠しクラスが生成されますか?それはメモリリークを引き起こすでしょうか?
私は webkit-devtools-agent を使用して、Node.jsプロセスのヒープマップを分析しました。プロセスを開始してヒープのスナップショットを撮り、その後10kのリクエストを送信して切断しました。その後、再度ヒープのスナップショットを撮りました。 comparisonパースペクティブを使用して、これら2つのスナップショットの違いを確認しました。ここに私が得たものがあります: 誰かこれを説明できますか? (配列)/(コンパイル済みコード)/(文字列)/コマンド/配列の数とサイズが大幅に増加しましたが、これはどういう意味ですか?
[〜#〜] edit [〜#〜]:ロードテストはどのように実行しましたか?
1。最初に、サーバーマシンとクライアントマシンの両方でいくつかのパラメーターを変更しました(60k以上の同時実行を実現するには、1つのマシンに最大60k +ポート(16ビットで表される)しかないため、複数のクライアントマシンが必要です)
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つのクライアントプログラムを起動しました。
[〜#〜] edit [〜#〜]:1台のマシン(2GB RAM)で25万の同時接続に到達しましたが、あまり意味がなく、実用的ではありません。なぜなら、接続が接続されたときは、接続を保留するだけで、それ以外は何もしないからです。それらに応答を送信しようとしたときに、同時実行数は約15万に落ちました。私が計算したように、接続ごとに約4KBのメモリ使用量があります。これはnet.ipv4.tcp_wmemに関連していると思います4096 16384 33554432に設定しましたが、変更さえしました小さくしても、何も変わりません。理由がわかりません。
[〜#〜] edit [〜#〜]:実際、今では、tcp接続ごとに使用されるメモリの量と、単一の接続で使用されるメモリの正確な構成に関心があります。私のテストデータによると:
150kの同時実行により約1800MのRAMが消費され(free -m出力から)、Node.jsプロセスには約600MのRSSがありました
次に、これを想定しました:
(1800M-600M)/ 150k = 8k、これはカーネルですTCP単一接続のスタックメモリ使用量、2つの部分で構成されます:読み取りバッファー(4KB)+書き込みバッファー(4KB)(実際、これは上記のnet.ipv4.tcp_rmemおよびnet.ipv4.tcp_wmemの設定と一致しません。これらのバッファに使用するメモリの量は、システムによってどのように決定されますか?)
600M/150k = 4k、これは単一接続のNode.jsメモリ使用量です
私は正しいですか?両方の側面でメモリ使用量を削減するにはどうすればよいですか?
うまく説明できなかった箇所があれば、教えてください。説明やアドバイスをいただければ幸いです。
メモリ使用量がさらに減少することを心配する必要はないと思います。あなたが含めたその読み出しから、あなたは考えられる最低限の最小限にかなり近いようです(私はそれがバイトであると解釈します、これはユニットが指定されていないときの標準です)。
これは私が答えられるよりも深い質問ですが、これが [〜#〜] rss [〜#〜] です。ヒープは、私が理解しているように、UNIXシステムで動的に割り当てられたメモリの場所です。したがって、ヒープの合計は、使用量に応じてヒープに割り当てられるすべての量であるように見えますが、使用されるヒープは、割り当てられた量のどれだけを使用したかです。
メモリ使用量は非常に良好で、実際にリークが発生しているようには見えません。私はまだ心配していません。 =]
分からない。
このスナップショットは妥当なようです。リクエストの急増から作成されたオブジェクトには、ガベージコレクションされたものとされなかったものがあると思います。 10kを超えるオブジェクトはなく、これらのオブジェクトのほとんどは非常に小さいことがわかります。いいと思います。
しかし、もっと重要なことには、これをどのように負荷テストしているのでしょうか。私は以前にこのような大規模な負荷テストを実行しようとしましたが、ほとんどのツールは、オープンファイル記述子の数に制限があるため(通常、デフォルトでプロセスあたり約1000)、Linuxでそのような負荷を生成することができません。 )。また、いったんソケットを使用すると、すぐに再び使用できるようになるわけではありません。私が覚えているように、再び使用可能になるまでには、1分単位のかなりの時間がかかります。これと、システム全体のオープンファイル記述子の制限が100k未満のどこかに設定されているのを私が通常見たという事実の間で、未変更のボックスでそれほど多くの負荷を受け取ること、または単一のボックスでそれを生成することが可能かどうかはわかりません。そのような手順については何も触れていないので、負荷テストを調べて、意図したとおりに機能していることを確認する必要があるかもしれません。
ほんのいくつかのメモ:
Resをオブジェクト{res:res}でラップする必要がありますか?
pendingClinets[req.clientId] = res;
[〜#〜] edit [〜#〜]役立つかもしれない別の〜micro最適化
server.emit('request', req, res);
2つの引数を 'request'に渡しますが、リクエストハンドラーは実際には応答 'res'のみを必要とします。
res['clientId'] = 'whatever';
server.emit('request', res);
実際のデータの量は同じままですが、「リクエスト」ハンドラの引数リストの引数が1つ少ないと、参照ポインタ(数バイト)が節約されます。しかし、何十万もの接続を処理している場合、数バイトが加算される可能性があります。また、emit呼び出しで追加の引数を処理する際のCPUオーバーヘッドも節約できます。