50
52

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Socket.IO(v1.3.5)+Redis+pm2+NginxでWebSocketサーバをスケールさせる

Posted at

サンプルコード

新バージョン:Socket.IO(v1.3.5) + pm2(forkモード)
旧バージョン:Socket.IO(v0.9) + pm2(clusterモード)

前置き

Socket.IOのv1.0でRedisを使ったスケールの仕方が変わり、
pm2のclusterモード(Node.jsのclusterモジュール)を使っていたv0.9用のスケールの仕方がそのままでは通用しなくなりました。

v0.9用のスケールの仕方に関する参考情報
Socket.IO, Redisを使用し各ゲーム間でプッシュ通知するシステム
Node.js + Socket.IO + pm2でデーモン化とクラスタリング
Express / Socket.IO をスケールアウトしてみよう

目的

最新のSocket.IO(v1.3.5)とpm2を使ってWebSocketサーバをスケールさせる方法を調べました。

本家のドキュメントを見ると、

  1. Nginxのip_hashを使う
  2. sticky-sessionのようなモジュールを使ってサーバサイドコードレベルでstickyロードバランスさせる
    のどちらかのアプローチがありそうです。

Socket.IO v0.9とv1.0以降との違いは、v0.9ではリクエストがどのサーバプロセスで処理されてもSocket.IOがうまく扱ってくれたのに対し、
v1.0以降ではNginxかサーバサイドコードでリクエストを同じサーバプロセスで処理させる必要があります。

今回は1.のNginxを使ってロードバランスさせる方法を解説します。
これは既にSocket.IO v0.9+pm2(clusterモード)でスケールさせていたコード(サンプルコードの旧バージョンみたいなやつ)があり、
サーバサイドのコードにあまり手を入れずにSocket.IO v1.3.5に対応させたかったからです。

構成

  • Node.js v0.12.5
  • Socket.IO v1.3.5
  • socket.io-redis v0.1.4
  • PM2 v0.14.2
  • Redis v3.0.2
  • Nginx v1.8.0

すべての設定は手元のMacでlocalhost前提で設定しています。
Nginx,Node,Redisが動くマシンが異なる場合などは環境に合わせて適宜変更してください。

PM2のモードはclusterかforkか?

本家のドキュメントにある通り、Node.jsのclusterモジュールを使う場合はsticky-sessionなどを使うようにサーバサイドのコードを変更する必要があります。
従って残念ながらpm2のclusterモードは今のところ使えません。
どのワーカープロセスにリクエストが処理されるかわからないためです。

pm2のforkモードで別々のportを割り当てたWebSocketサーバを起動し、Nginxでロードバランスさせます。
pm2のforkモードでCPU数に応じて動的にサーバのプロセスを起動する方法はこちらの投稿を参照してください。
pm2のforkモードで動的にCPUと同じ数のhttpサーバを起動する

server.jsとpm2の設定ファイル

以下のようにサーバサイドコードとpm2の設定ファイルを書いて $ pm2 start process.json と実行するとCPUの数だけport8080〜808xに割り当てられたWebSocketサーバが起動します。

server.js
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var redis = require('socket.io-redis');
io.adapter(redis({ host: 'localhost', port: 6379}));

var port = 8080 + parseInt(process.env.NODE_APP_INSTANCE || 0); // CPU数と同じ個数のプロセスが起動する

app.get('/', function(req, res) {
  res.sendFile(__dirname + '/index.html');
});

io.on('connection', function(socket) {
  socket.on('chat message', function(msg) {
    io.emit('chat message', "port:" + port + " user:" + socket.id + " msg:" + msg);
  });
});

http.listen(port, function() {
  console.log('listening on *:' + port);
});

process.json
{
  "name": "socketio-scale-sample",
  "script": "server.js",
  "exec_mode": "fork_mode",
  "instances": 0
}

Nginxでロードバランス

本家のドキュメントと同じように設定します。

conf/conf.d/default
upstream io_nodes {
  ip_hash;
  server 127.0.0.1:8080;
  server 127.0.0.1:8081;
  server 127.0.0.1:8082;
  server 127.0.0.1:8083;
}

server {
  listen 80;
  server_name localhost;
  location / {
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $host;
    proxy_http_version 1.1;
    proxy_pass http://io_nodes;
  }
  # 〜省略〜
}

スケールされてるか試す

Nginxとpm2を起動してhttp://localhost/ にアクセスすればWebSocketを使ったチャットアプリにつながります。

何かコメントを書くと「port:リクエストを処理したWebSocketサーバのポート user:Socket.IOのID msg:チャットメーリングリスト」の形式でメッセージが表示されます。
アプリを起動しているマシンとは別のIPアドレスを持つPC,タブレット,スマホなどからアクセスすれば、portが異なるサーバプロセスで処理されていることがわかります。socketio.png

同じマシン上の別ブラウザや別タブでアクセスしても、IPが同じため常に同じWebSocketサーバで処理されることに注意してください。

さらなるスケール

もしさらにWebSocketサーバを増やす必要に迫られたら、
WebSocket用のサーバマシンを追加し、
以下のようにNginxの設定を変えれば良いと思います。

conf/conf.d/default
upstream io_nodes {
  ip_hash;
  # WebSocketサーバ1用の設定
  server server1:8080;
  server server1:8081;
  server server1:8082;
  server server1:8083;

  # WebSocketサーバ2用の設定
  server server2:8080;
  server server2:8081;
  server server2:8082;
  server server2:8083;
  # ・・・
  # WebSocketサーバN用の設定
  server serverN:8080;
  server serverN:8081;
  server serverN:8082;
  server serverN:8083;
}

io_nodesの設定ファイルの行数がスゴイことになりそうですね・・・。
このあたりはもっと考える必要があるかもしれません。

Socket.IOv0.9 + pm2(clusterモード)との変更点

pm2のclusterモードで起動できなくなったため、pm2のGraceful reloadは使えません。
pm2のGraceful reloadに頼るような運用をしていた場合は、pm2のrestartで問題がないか調査・確認が必要だと思います。

まとめ

Socket.IO(v1.3.5) + pm2でスケールするWebSocketサーバを構築するとこんな感じになると思います。

Socket.IO(v1.3.5) + pm2を使ってスケールさせる情報は少ないのでこれがベストかはわかりませんがちょっとでも参考になれば幸いです。
突っ込みどころがあれば遠慮なくお願いします!

50
52
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
50
52

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?