Socket.IOをRedisで分散する

ここ最近、Socket.IOとRedisをいじっていてはまったりしたものの、RedisでSocket.IOのメッセージを分散できたようなのでそれのメモ。

  1. 2つのnode.jsが立っていて、それぞれクライアントからメッセージを受け取る
  2. 2つのnode.jsは1つだけあるredisのmasterにメッセージを送る
  3. redisのmasterは2つあるslaveそれぞれにメッセージをPUBLISHをする
  4. 2つあるredisのslaveはSUBSCRIBEでメッセージを受け取る
  5. 2つのnode.jsはそれぞれが監視しているredisのslaveからメッセージを受け取る

図としては以下のような感じ。(わかりづらい)

       <---         <----------------------------
client      node.js                               redis(slave)
       --->         ----                     --->
                       |                     |
                       |                     |
                       --->               ----
                            redis(master)
                       --->               ----
                       |                     |
                       |                     |
       --->         ----                     --->
client      node.js                               redis(slave)
       <---         <----------------------------

package.jsonは以下のとおり。

{
  "private": true,
  "dependencies": {
    "redis": "^0.12.1",
    "socket.io": "^1.2.1",
    "socket.io-emitter": "^0.2.0",
    "socket.io-redis": "^0.1.4"
  }
}

server.jsとして以下のようなものを書く。

var io = require('socket.io')(process.env.PORT || 3000),
    redis = require('socket.io-redis');

io.adapter(redis({
  pubClient:
    require('redis').createClient(
        process.env.PUB_PORT,
        process.env.PUB_HOST,
        {}),
  subClient:
    require('redis').createClient(
        process.env.SUB_PORT,
        process.env.SUB_HOST,
        { return_buffers: true })
}));

io.on('connection', function(socket) {
  console.log('connection');

  socket.on('message', function(data) {
    console.log('message');
    console.log(arguments);

    socket.broadcast.emit('message', data);
  });

  socket.on('disconnect', function() {
    console.log('disconnect');
    console.log(arguments);
  });
});

socket.io-redisにredisのインスタンスを渡す際に、return_bufferstrueに指定されていないとnode.jsが落ちる(OS X 10.10.1)ことがあった。

指定しなくても落ちない環境もある(OS X 10.9.5)のだけど、理由がわからない。


実際に動作させる。

Redisを起動する。以下には3つコマンドを書いているが、それぞれ別のセッションから実行して起動する。

デーモン化してしまえば1つのセッションで済むのだが、今回はログを見たかったのでtmuxの画面分割をして3つとも表示しておいた。

$ redis-server --port 6379 --loglevel debug
$ redis-server --port 6380 --loglevel debug --slaveof 127.0.0.1 6379
$ redis-server --port 6381 --loglevel debug --slaveof 127.0.0.1 6379

node.jsでSocket.IOサーバを起動する。

これもRedisと同様にそれぞれ別のセッションから実行する。

$ DEBUG=* PORT=3000 PUB_HOST=127.0.0.1 PUB_PORT=6379 SUB_HOST=127.0.0.1 SUB_PORT=6380 node server.js
$ DEBUG=* PORT=3001 PUB_HOST=127.0.0.1 PUB_PORT=6379 SUB_HOST=127.0.0.1 SUB_PORT=6381 node server.js

クライアントにはiocatを使う。

これもRedisと同様にそれぞれ別のセッションから実行する。

$ iocat -v --socketio http://127.0.0.1:3000
$ iocat -v --socketio http://127.0.0.1:3001

これで、それぞれのiocatから適当に文字を入力してEnterを押すと一方に入力した文字が表示されると思う。

ついでに、socket.io-emitterからそれぞれのクライアントに対してメッセージを送ってみる。

$ node
> var s = require('socket.io-emitter')({ host: '127.0.0.1', port: 6379 });
undefined
> s.emit('message', 'Hello!');

Hello!が送られていると思う。


今週はSocket.IOとRedisをずっと調べていて、少しはわかるようになってきた。

上記のものだとRedisのmasterが単一障害点となっているので、それをどうすれば解決できるのか気になる。いくらか考えて調べてみたけど機能的にできなかったりした。

この構成でどれくらいのパフォーマンスがになるのか調べていないので、それを先に試験をしてみたほうが良いかもしれない。