LoginSignup
32
33

More than 5 years have passed since last update.

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

Posted at

前回のSocket.ioサーバをスケールアウトするでは、sticky-sessionモジュールを使用してSocket.ioサーバを複数プロセスで運用する方法について書いた。

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

この問題を解決するために、Socket.ioではsocket.io-redisという拡張Adapterを用意している。メッセージのルーティングを担うモジュールのことをSocket.ioではAdapterと呼んでいて、socket.io-redisはデフォルトのAdapterである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}`);
    });
  });
}

2つの別々のホストからこのサーバに接続する。
※クライアントのプログラムはこちら

# クライアント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サーバワーカーが4つ起動しているから。

$ 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に送信することができるという仕組み。

32
33
1

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
32
33