Node におけるスケールアーキテクチャ考察(Scale 編)というエントリーを読んで、RedisはPub/Sub型通信をサポートしているという事を知りました。エントリーでも言及されているように、Pub/Subを使えば Node.js + WebSocket サーバをスケールする際に、中継サーバの役割を果たす事が出来るはずです。
そんな訳で実際に Node.js と Redis を使って Pub/Sub の実験を行なってみました。ユーザが別々のNode.jsサーバに接続していてもWebSocketを通してメッセージのやり取りを出来るようにします。
イメージとしてはこんな感じです。
下準備
Ubuntuの場合は apt-get で1発でインストールする事が出来ます。
$ sudo apt-get install redis
npmでredisモジュールをインストールします。
$ npm install redis
Node.js から Redis の Pub/Sub を使ってみる
試しにPub/Subとはどんなものか試してみましょう。Node.jsのコンソールを起動します。
$ node
コンソールに下記コードを入力します。
var sys = require('sys');
var redis = require('redis');
var subscriber = redis.createClient(6379, 'localhost');
subscriber.subscribe('hoge channel');
subscriber.on("message", function(channel, message) {
sys.puts(channel + " :" + message);
});
別のターミナルで、下記コマンドを実行します。
$ redis-cli publish "hoge channel" "Hello World!"
Node.js のコンソール画面に 「Hello World!」と表示されるはずです。これがPub/SubのSubにあたります。
では、今度はNode.js側からメッセージを送ってみましょう。先ほどのredis-cliを実行したターミナルで下記コマンドを実行します。
$ redis-cli subscribe "hoge channel"
Reading messages... (press Ctrl-c to quit)
1. "subscribe"
2. "hoge channel"
3. (integer) 1
Node.jsのコンソールに下記コードを入力します。
publisher = redis.createClient(6379, 'localhost');
publisher.publish("hoge channel", "FooBar");
Node.js のコンソール画面と redis-cli を実行した画面両方に「FooBar」と表示されますね。これで Redis の Pub/Sub の動きは何となく掴めたかと思います。
WebSocket を使ったチャットもどきを実装する
// This program is free software. It comes without any warranty, to
// the extent permitted by applicable law. You can redistribute it
// and/or modify it under the terms of the Do What The Fuck You Want
// To Public License, Version 2, as published by Sam Hocevar. See
// http://sam.zoy.org/wtfpl/COPYING for more details.
var sys = require('sys')
, opts = require('opts')
, ws = require('websocket-server')
, redis = require('redis')
, server = ws.createServer()
, subscriber = redis.createClient(6379, 'localhost')
, publisher = redis.createClient(6379, 'localhost');
opts.parse([
{
'short': 'p',
'long': 'port',
'description': 'WebSocket Port',
'value': true,
'required': true
}
]);
subscriber.on("error", function(err) {
sys.debug(err);
});
publisher.on("error", function(err) {
sys.debug(err);
});
subscriber.subscribe("chat");
subscriber.on("message", function(channel, message) {
sys.puts(message);
server.broadcast(message);
});
server.addListener("connection", function(connection) {
sys.puts("client connected: " + connection.id);
connection.addListener("message", function(message) {
publisher.publish("chat", message);
});
});
server.addListener("close", function(connection) {
sys.puts("client disconnected: " + connection.id);
});
server.listen(opts.get('port'));
上記コードをapp.jsとして保存し、複数のポートで立ち上げます(別途 npm で websocket-server と opts をインストールする必要があります)。
$ node app.js -p 8001
$ node app.js -p 8002
$ node app.js -p 8003
$ node app.js -p 8004
Google Chrome の JavaScript コンソールを複数のタブで開き、下記のように入力します。
var connection = new WebSocket("ws://localhost:8001");
connection.onmessage = function(event) { console.log(event.data) }
connection.send("Hello!!");
複数のタブで connection.send(……) をたくさん入力してみると分かりやすいと思います。どのタブで入力してもメッセージが表示されるはずです。
まとめ
今回は Redis の Pub/Sub を試す目的でしたので、チャットプログラム自体はメッセージを broadcast する事しか出来ません。しかし、Redis を通してやり取りするデータ形式をJSONにしておけば、色々な情報を各々の node.js サーバで共有する事ができるので、実用性が上がるのではないでしょうか。
また、Redis自体もレプリケーションが可能なので中継サーバもスケールする事が出来ると思います(未検証)。