Socket.ioサーバをスケールアウトする（Redisを使った複数プロセス間でのブロードキャスト）

前回の[Socket.ioサーバをスケールアウトする](http://qiita.com/takehilo/items/1e0a6db440916f769b27)では、[sticky-session](https://github.com/indutny/sticky-session)モジュールを使用してSocket.ioサーバを複数プロセスで運用する方法について書いた。

sticky-sessionを使用することによってクライアントがサーバとコネクションを張れない問題は解決できたが、まだ課題は残っている。
それは、２つのクライアントが別々のSocket.ioサーバプロセスに接続した場合に、この２つのクライアント間でメッセージを送受信することができないという問題。
例えば、クライアントAがサーバXに接続し、クライアントBがサーバYに接続したとすると、現段階ではサーバXがクライアントAから受け取ったメッセージを、サーバYに知らせるすべがない。

この問題を解決するために、Socket.ioでは[socket.io-redis](https://github.com/socketio/socket.io-redis)という拡張Adapterを用意している。メッセージのルーティングを担うモジュールのことをSocket.ioではAdapterと呼んでいて、socket.io-redisはデフォルトのAdapterである[socket.io-adapter](https://github.com/socketio/socket.io-adapter)を置き換える。

socket.io-redisを使用したサーバの実装は以下の通り。
socket.io-redisをインポートして、Adapterとしてセットするだけ。

```index.js
const cluster = require('cluster');
const io = require('socket.io')();
const sticky = require('sticky-session');
const http = require('http');
const redis = require('socket.io-redis');  // 追加

const server = http.createServer((req, res) => {
  res.end('worker: ' + cluster.worker.id);
});

io.adapter(redis({host: '127.0.0.1', port: 6379}));  // 追加
io.attach(server);
isWorker = sticky.listen(server, 3000);

if (isWorker) {
  io.on('connection', (socket) => {
    console.log(`worker: ${cluster.worker.id}, connected, id: ${socket.id}`);

    socket.on('chat message', (user, message) => {
      data = `${message} from ${user}`;
      console.log(data);
      socket.broadcast.emit('chat message', data);
    });

    socket.on('disconnect', () => {
      console.log(`disconnected, id: ${socket.id}`);
    });
  });
}
```

２つの別々のホストからこのサーバに接続する。
※クライアントのプログラムは[こちら](http://qiita.com/takehilo/items/dd64d999058cb1dd2c79)

```
# クライアントA
$ node client.js A
connected, id: S1H5pJcm8Kun5lOoAAAA
```

```
# クライアントB
$ node client.js B
connected, id: 7HLiEmHioqbLuINtAAAA
```


```
# サーバ
$ node index.js
worker: 2, connected, id: S1H5pJcm8Kun5lOoAAAA
worker: 4, connected, id: 7HLiEmHioqbLuINtAAAA
```

サーバのログから、クライアントAとBは別々のSocket.ioサーバプロセスに接続されたことがわかる。
前回までの実装では、クライアントAが送信したメッセージをサーバブロードキャストしても、クライアントBがメッセージを受取ることはできなかったが、Redisを介した実装ではクライアントBがメッセージを受取ることができる。

```
# クライアントA
$ node client.js A
connected, id: S1H5pJcm8Kun5lOoAAAA

Hello  # クライアントAが送信したメッセージ

Hi from B  # クライアントBが送信したメッセージ
```

```
# クライアントB
$ node client.js B
connected, id: 7HLiEmHioqbLuINtAAAA

Hello from A  # クライアントAが送信したメッセージ

Hi  # クライアントBが送信したメッセージ
```

## 裏側で何が起きているか
Redisをモニターしていると、裏側でどんなやり取りがあるのかがわかる。
socket.io-redisは、RedisのPUB/SUBを利用している。

まず、socket.io-redisを実装したサーバを起動すると、サーバは`socket.io#/#`というチャンネルをSUBSCRIBEする（使用するネームスペースによって変わる）。
4行出力されているのは、Socket.ioサーバワーカーが４つ起動しているから。

```
$ redis-cli monitor
OK

"subscribe" "socket.io#/#" "socket.io-request#/#" "socket.io-response#/#"
"subscribe" "socket.io#/#" "socket.io-request#/#" "socket.io-response#/#"
"subscribe" "socket.io#/#" "socket.io-request#/#" "socket.io-response#/#"
"subscribe" "socket.io#/#" "socket.io-request#/#" "socket.io-response#/#"
```

次に、クライアントAがサーバXに接続してメッセージを送信すると、サーバはメッセージをRedisに対してPUBLISHする。

```
"publish" "socket.io#/#" "\x93\xa66n0BWC\x83\xa4type\x02\xa4data\x92\xacchat message\xacHello from A\xa3nsp\xa1/\x83\xa6except\x91\xb4S1H5pJcm8Kun5lOoAAAA\xa5rooms\xc4\xa5flags\x81\xa9broadcast\xc3"
```

`socket.io#/#`チャンネルに対してPUBLISHしているので、このチャンネルをSUBSCRIBEしているサーバYがメッセージの通知を受取り、それをクライアントBに送信することができるという仕組み。