Node.js + Socket.IO + pm2でデーモン化とクラスタリング
Socket.IOをpm2でクラスタリングするには、ちょっと工夫が必要だったのでメモ
forever
Node.jsのデーモン化といえばforeverです。しかしクラスタリングしようとすると、Node.jsのコードをクラスタリング対応で書かないといけないのでやや面倒だったりします。今回、foreverよりも高機能なpm2を使ってクラスタリングを試してみました。
pm2
pm2はforeverと同じようにNode.jsをデーモン化するツールですが、モニタ機能やクラスタリング機能などかなり高機能になっています。
pm2のインストール
pm2はグローバルでインストールします。
$ npm install -g pm2
対応するNode.jsのバージョンは古いと動かないかもしれません。今回はv0.10.20(on Mac)で動作確認しました。動かすNode.jsアプリはこちらのSocket.IOのサンプルアプリです。このコードだとSocket.IOのバージョンが古いようなので、一旦npm uninstall socket.ioして、再度npm install socket.ioで再インストールしました。
pm2を使ってクラスタリング
$ pm2 start app.js -i max
として起動するとプロセスがCPUコア数分だけ起動し、クラスタリングされます。簡単ですね。起動リストを表示させるには
$ pm2 list

プロセスが4つ起動されているのがわかります。この時にブラウザでアクセスして確認します。

片方がxhr-pollingになってたりしますね。これリロードするとwebsocketになったり、xhr-pollingになったりなんとも不安定になります。
ログを表示するには
$ pm2 logs

ところどころに警告がでています。
warn - client not handshaken client should reconnect
調べてみるとSocket.IOの接続に起因するものでSocket.IO or WebSocket を AmazonELB でバランスする検証にありました。
つまり、 Socket.IO は接続確立までに 2 回リクエストを投げるということです。 そしてこの最初の要求と、 WS の接続要求は、「同じインスタンス」にアクセスする必要があります。 二つの接続がバランスされて別々のサーバに行くと、ハンドシェイクがエラーになり、リトライが発生します。 たまたま二回同じサーバにいけば、接続が確立できるので、バランスするサーバが増えるほど成功確率が減 り、確立までの時間が長くなります。
redisの導入
解決策としてredisをセッションキーストアとして利用する、ということのようです。今更だけどSocket.ioについてまとめてみる
redisのPub/Subを利用して、各プロセス間でセッション共有をするということです。
このサイトの記事のとおりなのですが、ひと通りメモ
redisのインストールと起動
$ brew install redis
$ redis-server /usr/local/etc/redis.conf &
[1] 3234
$ [3234] 31 Jan 11:29:03.112 * Max number of open files set to 10032
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 2.8.1 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in stand alone mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 6379
| `-._ `._ / _.-' | PID: 3234
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
[3234] 31 Jan 11:29:03.113 # Server started, Redis version 2.8.1
[3234] 31 Jan 11:29:03.113 * The server is now ready to accept connections on port 6379
redisのpub/sub機能
redisにはpublisher/subscriberという機能があって、いずれかのプロセスが受けた通知をredisに設定することで、他のプロセスに通知を行い共有できるということです。この機能を使うことでクラスタリングされたプロセス間でセッションの共有を行えます。
サーバアプリの書き換え
app.jsを以下のように書き換えます
var app = express()
, server = http.createServer(app)
, redis = require('socket.io/lib/stores/redis') // socket.ioにredisがある
, redisConf = { host: '127.0.0.1', port: 6379 }
, io = require('socket.io').listen(server);
// storeタイプをredisに変更
io.set('store', new redis({
redisPub: redisConf,
redisSub: redisConf,
redisClient: redisConf
}));
pm2でクラスタリング
再びpm2でクラスタリングを試してみます。
$ pm2 start app.js -i max
この時のアクセスログを見ると警告が無くなり、かつ正常にクラスタリングされているようです\(^o^)/
