はじめに
本記事では、Node.js+Redisを用いたオーケストレーションサーバと、ブラウザクライアント(Web Worker含む)を実際に構築する手順を解説します。ソースコードを動かしながら、NodIOのコア実装を体験しましょう。
目次
- 環境準備
- オーケストレーションサーバ実装
- プロジェクト初期化
- WebSocketサーバ構築
- Redisタスクキュー連携
- タスク配信・結果受信ロジック
- ブラウザクライアント実装
- HTML & メインスクリプト
- Web Worker スクリプト
- 動作確認
- 次のステップ
1. 環境準備
- Node.js (v16+)
- Redis サーバ
- ブラウザ(最新Chrome推奨)
# プロジェクト用フォルダ作成
mkdir nodio-hands-on && cd nodio-hands-on
# Node.js プロジェクト初期化
npm init -y
# 必要パッケージのインストール
npm install express ws ioredis
2. オーケストレーションサーバ実装
2.1 プロジェクト構成
nodio-hands-on/
├─ server/
│ ├─ index.js
│ └─ tasks.js
└─ client/
├─ index.html
├─ main.js
└─ worker.js
2.2 WebSocketサーバ構築 (server/index.js
)
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const { enqueueTask, dequeueTask } = require('./tasks');
const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
const clients = new Map(); // nodeId → ws
let nextNodeId = 1;
wss.on('connection', socket => {
const nodeId = nextNodeId++;
clients.set(nodeId, socket);
console.log(`Node ${nodeId} connected`);
socket.on('message', async message => {
const msg = JSON.parse(message);
if (msg.type === 'RESULT') {
console.log(`Result from ${nodeId}:`, msg.payload);
// TODO: 検証・集約ロジック
}
});
socket.on('close', () => {
clients.delete(nodeId);
console.log(`Node ${nodeId} disconnected`);
});
});
// 定期タスク配信ループ
setInterval(async () => {
const task = await dequeueTask();
if (!task) return;
for (const [nodeId, ws] of clients) {
ws.send(JSON.stringify({ type: 'TASK', payload: task }));
break; // 単一ノードへ配信
}
}, 100);
server.listen(8080, () => console.log('Server listening on 8080'));
2.3 Redisタスクキュー連携 (server/tasks.js
)
const Redis = require('ioredis');
const redis = new Redis();
// サンプルタスク投入(起動時一度だけ)
(async () => {
await redis.lpush('tasks', JSON.stringify({ taskId: 't1', data: 42 }));
await redis.lpush('tasks', JSON.stringify({ taskId: 't2', data: 84 }));
})();
async function dequeueTask() {
const raw = await redis.rpop('tasks');
return raw ? JSON.parse(raw) : null;
}
async function enqueueTask(task) {
await redis.lpush('tasks', JSON.stringify(task));
}
module.exports = { dequeueTask, enqueueTask };
3. ブラウザクライアント実装
3.1 HTML & メインスクリプト (client/index.html
+ client/main.js
)
NodIO Client
NodIO クライアント
// client/main.js
const socket = new WebSocket('ws://localhost:8080');
socket.addEventListener('open', () => console.log('WebSocket connected'));
socket.addEventListener('message', e => {
const msg = JSON.parse(e.data);
if (msg.type === 'TASK') {
const worker = new Worker('worker.js');
worker.postMessage(msg.payload);
worker.onmessage = ev => {
socket.send(JSON.stringify({ type: 'RESULT', payload: ev.data }));
};
}
});
window.addEventListener('beforeunload', () => {
socket.close();
});
3.2 Web Worker スクリプト (client/worker.js
)
// client/worker.js
self.onmessage = e => {
const { taskId, data } = e.data;
// 例:データを二乗して返す簡易計算
const result = data * data;
postMessage({ taskId, result });
};
4. 動作確認
- Redisサーバ起動:
redis-server
- サーバ起動:
node server/index.js
- ブラウザで
client/index.html
を開く - サーバコンソールにタスク配信・結果受信ログを確認
5. 次のステップ
- フェイルオーバー:複数ノードへの同時配信とクロスチェック
- 検証ロジック:結果ハッシュ比較や閾値判定
- ダッシュボード:Prometheus/Grafana連携によるノード・タスク状況可視化
- WebAssembly対応:worker内をWASM化して高速化
シリーズ3では、こうしたパフォーマンス計測と最適化手法を詳述します。お楽しみに!