🛰️
WebSocketの最小実装からRedis連携まで:リアルタイム通信の基礎とスケールアウト対応
はじめに
リアルタイム通信を学ぶとき、いきなりRedisやKafkaを導入すると「なぜ必要なのか」が見えづらくなります。
この記事ではまず 最小限のWebSocketサーバー を動かして感覚を掴み、
その後に スケールアウト時の課題とRedisによる解決 を体験する流れで整理します。
Step 1. 最小構成でWebSocketを動かしてみる
構成図
[Browser] ⇄ (WebSocket) ⇄ [Node.js Server]
サーバー
server.js
import { WebSocketServer } from "ws";
const wss = new WebSocketServer({ port: 3001 });
wss.on("connection", (ws) => {
console.log("🔌 Client connected");
ws.on("message", (msg) => {
console.log("📨 Received:", msg.toString());
// 全クライアントに送信(同じプロセス内)
wss.clients.forEach((client) => {
if (client.readyState === 1) {
client.send(msg.toString());
}
});
});
});
console.log("🚀 WebSocket server on ws://localhost:3001");
クライアント
client.html
<!DOCTYPE html>
<html>
<body>
<h2>リアルタイムカウンター</h2>
<p>カウント: <span id="count">0</span></p>
<button id="inc">+1</button>
<script>
const socket = new WebSocket("ws://localhost:3001");
const countEl = document.getElementById("count");
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
countEl.textContent = data.count;
};
document.getElementById("inc").onclick = () => {
const newCount = parseInt(countEl.textContent) + 1;
socket.send(JSON.stringify({ count: newCount }));
};
</script>
</body>
</html>
実行手順
bash
node server.js
# そして client.html をブラウザで開く
→ 2タブ開いて一方で「+1」すると、両方の画面が同時に更新されます 🎉
⚠️ Step 2. スケールアウトすると破綻する
ECSなどでWebSocketサーバーをスケールさせると、ALBによって接続が複数タスクに分散されます。
[ALB]
├──▶ [Server #1] (clients A, B)
└──▶ [Server #2] (clients C, D)
各サーバーはメモリ内に自分のクライアントだけを保持しているため、
Server #1で発生したイベントをServer #2のクライアントに送ることができません。
Step 3. Redis Pub/Subでスケール対応
構成図
┌──────────┐
│ Redis │ ← Pub/Sub Broker
└────┬─────┘
│
┌───────────┴───────────┐
▼ ▼
[Server #1] [Server #2]
(clients A,B) (clients C,D)
修正版サーバー
server.js
import { WebSocketServer } from "ws";
import { createClient } from "redis";
const wss = new WebSocketServer({ port: process.env.PORT || 3001 });
const redisSubscriber = createClient({ url: "redis://localhost:6379" });
const redisPublisher = createClient({ url: "redis://localhost:6379" });
await redisSubscriber.connect();
await redisPublisher.connect();
await redisSubscriber.subscribe("counterUpdated", (message) => {
wss.clients.forEach((client) => {
if (client.readyState === 1) client.send(message);
});
});
wss.on("connection", (ws) => {
ws.on("message", async (msg) => {
await redisPublisher.publish("counterUpdated", msg.toString());
});
});
Redisをブローカーとして利用することで、
どのサーバーでイベントが発生しても全サーバーに配信できるようになります。
Redis導入の効果まとめ
| 項目 | 導入前(単体) | 導入後(Redis連携) |
|---|---|---|
| 通信範囲 | 同一サーバー内のみ | 複数サーバー間で共有 |
| ECSスケール対応 | ❌ 不可 | ✅ 可 |
| 状態共有 | メモリのみ | Redis経由 |
| 冗長構成 | 不可 | 可能 |
Redisは「イベントブローカー」として、
どのサーバーからでも、全クライアントに同じイベントを届ける 役割を果たします。
Discussion