19
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

WebsocketとRedis Pub/Sub

Last updated at Posted at 2020-12-29

RedisのPub/Sub機能を使うとWebsocketの通信情報を複数のサーバー間で同期させることができます。
スケールのためにWebsocketのサーバーを複数立てた場合、サーバー間で通信情報を同期する仕組みがないと、例えばチャットアプリケーションの場合、サーバーAに接続されたクライアントはサーバーBに接続されたクライアントとは正常にメッセージのやりとりができないことになってしまいます。
RedisのPub/Sub機能はそれを解消することができます。

スクリーンショット 2020-12-28 20.44.39.png

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!"のメッセージが届きます。
スクリーンショット_2020-12-27_17_42_18.png

Node.jsでサンプルを動かす

Node.jsでwebsocket + Redis PubSubを使った簡単なサンプルです。ソースコードはこちら

タブを複数開き、一方でメッセージを送ると両方で更新されます。※この時点ではまだpubsubは関係ありません
ezgif.com-crop.gif

まずはクライアント側です。接続ボタンが押されたらサーバーにアクセスします。サーバーからメッセージを受け取ったら画面に表示します。

public/client.js
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つ作っています。

server.js
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'チャンネルに新規メッセージが来たら、全てのクライアントにメッセージを配信します。

server.js
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でアクセスできるようにしています。

docker-compose.yml
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にアクセスします。
一方のタブで送信したメッセージが、もう一方にも表示されれば成功です。
ezgif.com-crop.gif

実際のアプリケーションはより複雑にはなりますが、概要を書きました。
より実践的なアプリケーション構成や実装方法は、参考にあるAWSのブログが分かりやすいと思います。

参考

19
14
0

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
19
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?