node.js の負荷分散について考えてみました (フェイルオーバは考慮できていません).個人レベルなので 1 台のハード上に仮想マシンを 5〜6 個立ち上げて実験しています.
見出し
- はじめに
- cluster で負荷分散
- 寄り道:cluster の仕組み
- 例えばこんな全体構成
- おわりに
はじめに
node.js は設計上,大量のコネクションを省リソース (プロセス・スレッドをバカスカ生成しない) でさばきます.おそらく想定されているのは I/O バウンドな処理であり,この場合は基本的に非同期で処理されるため,I/O 待ちで他のリクエスト処理がブロックすることはまずありません.
node.js は「サービスをつなぎ・組み合わせるためのハブ」的な位置づけが一番しっくりくるように感じます *1.
ただ,
- 大量のリクエストをさばかなければならない
- ロジックが重くてコールバック処理に負荷がかかってしまう
等といった場合では,CPU 数に応じて負荷分散させたくなります.
また,Web アプリケーションの静的コンテンツ (js, css, image 等) の配信まで node.js ノードに任せるのは明らかに非効率ですよね.
cluster で負荷分散
この記事 (http://blog.asial.co.jp/807) を読んで cluster を試してみたところ,大変使いやすい.express のコミッタが開発しているためか,express との相性も良いと感じます.
おなじみ express コマンドでスケルトンを生成し,以下のように書き換えます.
// app.js var express = require('express'); var app = express.createServer(); app.configure(function(){ app.set('views', __dirname + '/views'); app.set('view engine', 'jade'); app.use(express.logger()); app.use(express.bodyParser()); app.use(express.methodOverride()); app.use(app.router); app.use(express.static(__dirname + '/public')); }); app.get('/', function(req, res){ res.render('index', { title: 'Express', worker_id: process.env.CLUSTER_WORKER // worker の ID が取れる }); }); module.exports = app;
<!-- views/index.jade --> h1= title p Welcome to #{title} p worker #{worker_id}
// cluster.js var cluster = require('cluster'); cluster('./app') .use(cluster.debug()) // debug ログが出るので,動作が分かりやすくなる .use(cluster.logger()) .use(cluster.pidfiles()) .on('close', function() { console.log('cluster end'); }) .listen(8001);
$ node cluster.js して F5 アタック?をかけると,worker の切り替わる様子が分かります.
Readme の Example で 'recommended usage' とあるように,cluster 関数にファイル名 './app' を指定すると,master プロセスでは require('./app') が発生せず,worker プロセスの場合にのみ require('./app') されます (docs/api).
また,use(cluster.repl(port)) することで簡単な管理コンソールを起動することができます (docs/repl).
分散してもセッションを維持したい場合は,express で connect-redis を利用するのが一番手軽です.
寄り道:cluster の仕組み
cluster は以下のような構成で複数の node.js プロセスを管理しています.
Worker プロセス数は外部から指定することも可能ですが,デフォルトでは CPU 数だけ起動します.また,プロセス間のやりとり (赤点線矢印) は JSON で独自の RPC をしています (JSON-RPC とは異なる仕様).
プロセスとインスタンスの構成は以下のようになっています.コードを読む際はこの構成を念頭に置くと,理解しやすくなります.
例えばこんな全体構成
例えばこんな全体構成.動作確認用アプリとして チャットアプリ をイメージしており,手元の環境では動作しています *2.
クライアントからは HTTP リクエストと socket.io (WebSocket) による通信が発生します.
サーバ側では,リバースプロキシ (の設定をした nginx) を配置し,静的コンテンツは自身で配信,動的コンテンツについてはバックエンドの cluster にリクエストを投げます.設定としてはこんな感じです.
# /etc/nginx/conf.d/proxy.conf として以下の内容を作成します (IP は適当です). # ↓ロードバランサ設定の場合 # upstream clusters { # server 192.168.11.20:8001; # server 192.168.11.20:8002; # } server { listen 8080; server_name 192.168.11.10; location / { proxy_pass http://192.168.11.20:8001; # proxy_pass http://clusters; } location /images/ { alias /var/www/public/images/; } location /stylesheets/ { alias /var/www/public/stylesheets/; } location /javascripts/ { alias /var/www/public/javascripts/; } }
cluster マシンを増やす場合は nginx でロードバランシングさせるか,LVS + keepalived (←こっちはやったことない) で分散させます.
cluster 内の node.js では,express でメイン画面を,リアルタイム通信には socket.io を利用しています.もう鉄板ですね.セッション維持には redis を利用しています.
relay server は,各 node.js プロセスで発生した Socket.io のイベントを別の node.js プロセスに伝達するための中継サーバです.今回は実験ということもあって,適当にこしらえました.
メイン DB には mongoDB を利用しています (後に作成予定のアプリにとって都合が良いため).Replica Set 便利ですね.今回は sharding しないので 1 セットだけです.express からの接続には mongoose を利用しています.
参考:
- load balancer algorithm
- load balance 設定
- Socket.io とのセッション共有
- mongoDB Replica Set
おわりに
今回は node.js の負荷分散について試してみました.cluster の簡単な解説と,全体構成を示しています.
一応,proxy から DB まで一通りつないで実験していますが,Socket.io と cluster の組み合わせに問題がないのかを調査し切れていません.また,はやいところ計測を済ませてしまいたいです.
あとはテストについて調査をしたら,そろそろアプリを作り始めても良いんじゃないかな,KrdLab よ.