RedisのPub/Sub機能を使うとWebsocketの通信情報を複数のサーバー間で同期させることができます。
スケールのためにWebsocketのサーバーを複数立てた場合、サーバー間で通信情報を同期する仕組みがないと、例えばチャットアプリケーションの場合、サーバーAに接続されたクライアントはサーバーBに接続されたクライアントとは正常にメッセージのやりとりができないことになってしまいます。
RedisのPub/Sub機能はそれを解消することができます。
Redis Pub/Subとは
Pus/Subにでてくる主な概念です。
用語 | 説明 |
---|---|
Publish | 発行すること |
Subscribe | 購読すること |
Channel | 購読するチャンネル |
ラジオで言えば
番組を配信することがPublish、
番組を聞くことがSubscribe、
番組そのものがChannelです。
ラジオの音は、番組を聞いている人にだけ配信されます。
Redis-cliを使ってpubsubを試してみます。
redis-server //Redisを起動
redis-cli //CLIを起動
ターミナルのタブを二つ開き、一方のターミナルで
SUBSCRIBE MorningShow
もう一方のターミナルで
PUBLISH MorningShow "Good Morning!"
を実行すると、Subscribeしている側のタブに"Good Morning!"のメッセージが届きます。
Node.jsでサンプルを動かす
Node.jsでwebsocket + Redis PubSubを使った簡単なサンプルです。ソースコードはこちら。
タブを複数開き、一方でメッセージを送ると両方で更新されます。※この時点ではまだpubsubは関係ありません
まずはクライアント側です。接続ボタンが押されたらサーバーにアクセスします。サーバーからメッセージを受け取ったら画面に表示します。
wsButton.onclick = function () {
wsSendButton.disabled = false;
ws = new WebSocket(`ws://${location.host}`);
ws.onopen = function () {
showMessage('WebSocket connection established');
};
ws.onmessage = function (message) {
let data = JSON.parse(message.data)
showMessage(JSON.stringify(data.message));
};
-- 省略 --
};
次にサーバー側です。
websocketのリスナーを作成と、Redisへの接続を行います。今回はdocker-composeで立てたRedisコンテナに接続するため、Redisホストをコンテナ名の'redis'としています(参照)。publishとsubscribe用にRedisへの接続は2つ作っています。
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const Redis = require('ioredis');
const app = express();
app.use(express.static('public'));
const server = http.createServer(app);
const wss = new WebSocket.Server({ noServer: true });
const redis = new Redis('redis'); //redis container
const client = new Redis('redis');
次がpubsubの処理です。
あらかじめ一方のRedisへの接続で'newMessage'というチャンネルをsubscribeしておきます。
クライアントからメッセージを受け取ったら、もう一方の接続で'newMessage'のチャンネルに対して受け取ったメッセージをpublishします。
'newMessage'チャンネルに新規メッセージが来たら、全てのクライアントにメッセージを配信します。
function subscribeMessage(channel) {
client.subscribe(channel);
client.on('message', function(channel, message) {
broadcast(JSON.parse(message))
});
}
subscribeMessage('newMessage')
// broadcast message to all clients
function broadcast(message){
wss.clients.forEach(function(client){
client.send(JSON.stringify({
message: message
}))
})
}
wss.on('connection', function (ws, request) {
ws.on('message', function (message) {
redis.publish('newMessage', JSON.stringify(message))
});
-- 省略 --
});
ここまでは一台のサーバーで動かしてもpubsubを使う意味がありません。
なので、次にNode.jsのdockerコンテナを2つ立て、一方のコンテナにメッセージが来たらもう一方のコンテナにもpubsubを通して同期されることを試します。
Node.jsのコンテナ間で通信を同期させる
Node.jsのコンテナを立てるDockerfileです。公式にあるものをそのまま利用してます。
FROM node:12
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
CMD [ "node", "server.js" ]
Dockerfileを元にNode.jsのコンテナを2つ、またRedisコンテナをdocker-composeで作成します。
Nodeのコンテナは一つをlocalhost:3000、もう一つをlocalhost:3030でアクセスできるようにしています。
version: '3'
services:
server1:
build: .
ports:
- 3000:3000
server2:
build: .
ports:
- 3030:3000
redis:
image: redis:6
ports:
- 6379:6379
最後にコンテナを起動します。
docker-compose up --build
起動したら、ブラウザのタブを二つ開き、一方をlocalhost:3000、もう一方をlocalhost:3030にアクセスします。
一方のタブで送信したメッセージが、もう一方にも表示されれば成功です。
実際のアプリケーションはより複雑にはなりますが、概要を書きました。
より実践的なアプリケーション構成や実装方法は、参考にあるAWSのブログが分かりやすいと思います。