-
Notifications
You must be signed in to change notification settings - Fork 9
realtime polyglot app node ruby mongodb socketio
リアルタイムのアプリ、または、プッシュベースのインタラクティビティを組み込んだイベント駆動型アプリは、 チャット、ラージスケールのゲーム、共同編集作業、低待ち時間での通知といった、ブラウザ内での動作の可能性を高める 新しい技術の基礎となります。たくさんのテクノロジーが、この流れをサポートしていますが、 以下の4つのテクノロジーが突出しています。: Ruby, Node.js, MongoDB そして Socket.IOです。
この記事では、これらの技術を使用したリアルタイムのアプリを構築するためのアーキテクチャとコードの両方を学ぶことが可能です。
この記事のRuby data-writer componentのサンプルコードと Node.jsのWebアプリケーションはGitHub上で利用可能で、 動作に関しては、http://tractorpush.herokuapp.com/で確認可能です。
サンプルのTractorPushリアルタイムアプリケーションは、MongoDbのキューにメッセージを格納するために Rubyベースのデータコンポーネントを使用し、次にそのキューがNode.jsのWebアプリに受信され、 Socket.IOを経由してユーザーのブラウザへ表示されます。 要するに全てのスタックは、プッシュ通知により動作します。
このシステムは、システム全体を作り上げるそれぞれの独立したアプリケーション同士を結びつける「糊」として動作する 後方サービス(MongoDB)を共有し、実装されます。
キューは、相互運用性があるものの、独立したプロセスを記述するためにパワフルなメカニズムとなります。 商用的に実行可能なソリューションとして何百はなくとも、何十も存在しています。 古くから存在し、広く実装されているIBM WebSphere MQに始まり、 より新しくオープンソースかつオープンスタンダードなRabbitMQ や ZeroMQがあります。
MongoDBは、その柔軟なドキュメント保存の特性、多岐に渡る各言語へのサポート、 そしてtailable cursor の「プッシュ」の特徴により、 有用な多言語のメッセージキューとして役立ちます。 複雑で勝手気ままなJSONメッセージのマーシャリングとアンマーシャリングは自動的にハンドリングされます。 Safe-writeは、改良されたメッセージの耐久性と信頼性に対し効果があり、 tailable cursorは、MongoDBからNode.jsへの「プッシュ」を行うために使われます。
Rubyの成熟したWebフレームワーク(Rails、 Sinatra)は、しばしばメッセージキューが源泉となる多くの Webアプリにとって理想的です。TractorPushは、このようなアプリをRubyで書かれたdata-writerにてシミュレートします。
GitHubからアプリをクローンし、Heroku上にアプリをクリエートして下さい。
:::term
$ git clone https://github.com/mongolab/tractorpush-writer-ruby.git
Cloning into tractorpush-writer-ruby...
...
Resolving deltas: 100% (8/8), done.
$ cd tractorpush-writer-ruby
$ heroku create tp-writer
Creating tp-writer... done, stack is cedar
http://tp-writer.herokuapp.com/ | git@heroku.com:tp-writer.git
Git remote heroku added
メッセージキューを管理することになるMongoDBのインスタンスを生成するため、MongoLabのアドオンを設定して下さい。
:::term
$ heroku addons:add mongolab
----> Adding mongolab to tp-writer... done, v2 (free)
Welcome to MongoLab.
MongoDBのCappedコレクションは、 tailable cursorをサポートしています。tailable cursorは、MongoDBがリスナーへデータをプッシュすることを許可します。 もし、このタイプのカーソルが結果セットの終端に達した場合、例外を返す代わりに、新たなドキュメントを返却し続け、 そのドキュメント群がコレクションにインサートされるまでブロックします。
また、Cappedコレクションは非常に高いパフォーマンスを発揮します。事実、MongoDBは、オペレーションlog (または、oplog)を 保存するために、内部的にCappedコレクションを使っています。交換条件として、Cappedコレクションは固定となり (例えば、サイズが“capped”となる)、分割することが出来ません。但し、多くのアプリケーションにとって、これは許容範囲です。
TractorPushアプリケーションは、メッセージを保存する際、MongoDBのCappedコレクションに依存しています。
heroku addons:open mongolab
コマンドで、MongoLabアドオンのダッシュボードを開いて下さい。
"Add"を押下し、新規コレクションを追加して下さい。
この新規コレクションをmessages
と命名して下さい。また、詳細オプション画面を開け、
Cappedコレクションのサイズを8,000,000バイトと指定して下さい。(デモには十分なスペースとなります。)
Heroku上にメッセージ書き出しアプリを作成し、MongoLabのアドオンも設定済みの状態にしました。 次にHerokuへアプリケーションをディプロイしてみましょう。
:::term
$ git push heroku master
Counting objects: 20, done.
...
-----> Heroku receiving push
-----> Ruby app detected
...
-----> Launching... done, v4
http://tp-writer.herokuapp.com deployed to Heroku
TractorPushは、MongoDBのデータベースへメッセージを書き出すシンプルでヘッドレスなRubyプログラムです。
Procfile
を見てみましょう。
書き出しのプロセスが、worker
とラベルされています。メッセージキューを開始させるため、worker
プロセスを1dyno、
スケールして下さい。
:::term
$ heroku ps:scale worker=1
Scaling worker processes... done, now running 1
heroku ps
コマンドで、workerプロセスが実行されていることを確認して下さい。
:::term
$ heroku ps
=== worker: `ruby writer.rb`
worker.1: up for 47s
TractorPushアプリケーションは、MongoDBオブジェクトのマーシャリングとアンマーシャリングの柔軟性を デモンストレーションするために、3種類のドキュメントベースのメッセージを使います。: シンプル(または名前値と呼ばれる)、配列、そしてコンプレックス(またはネストされたドキュメントと呼ばれる)の3種類です。
このwriter.rb のスクリプトは、 上記3種類のドキュメントタイプの中から1つ、デフォルトレートである1秒毎に、MongoDBのコレクションへ書き出しを行います。
:::ruby
while(true)
coll.insert(doc, :safe => true)
sleep(rate)
end
:safe
の書き出しオプションは、データベースがエラー無しでメッセージドキュメントを受け取り、応答することを保証します。
コレクションの中身とドキュメント数の増加を確認するために、`heroku addons:open mongolab`コマンドで、 MongoLabアドオンのダッシュボードへアクセスすることも可能です。
メッセージキューのタイプを表示するログを確認します。
:::term
$ heroku logs -t --ps worker.1
2012-03-23T14:56:35+00:00 app[worker.1]: Inserting complex message
2012-03-23T14:56:36+00:00 app[worker.1]: Inserting simple message
2012-03-23T14:56:37+00:00 app[worker.1]: Inserting simple message
2012-03-23T14:56:38+00:00 app[worker.1]: Inserting array message
`messages`コレクションがCappedコレクションであるため、コレクションのサイズが制限値を越えた場合、 古いドキュメントは破棄されます。
このシステムの利用者側は、Cappedコレクションからのメッセージを利用する Node.jsのwebアプリケーション となります。 ローカルにアプリをクローンし、Heroku上にアプリをクリエートして下さい。
:::term
$ git clone https://github.com/mongolab/tractorpush-server.git
Cloning into tractorpush-server...
...
Resolving deltas: 100% (8/8), done.
$ cd tractorpush-server
$ heroku create tp-web
Creating tp-web… done, stack is cedar
http://tp-web.herokuapp.com/ | git@heroku.com:tp-web.git
Git remote heroku added
単一システムとして、2つの言語環境(Node.jsとRuby)を使用するために、この2つアプリケーションは、メッセージストアを
共有する必要があります。書き出しアプリとwebアプリ間でMongoDBのインスタンスを共有して下さい。
書き出しアプリからMONGOLAB_URI
環境変数をコピーし、Node.jsのwebアプリへセットすることで
共有可能となります。
:::term
$ heroku config:add -a tp-web `heroku config -a tp-writer -s | grep MONGOLAB_URI`
Adding config vars and restarting app... done, v23
MONGOLAB_URI => mongodb://heroku...eroku_app123456
`tp-writer`から`mongolab`のアドオンをリムーブする場合、または、アプリ自体を削除する場合、 たとえ`tp-web`から参照されていたとしても、データベースを元のように供給し直すことが出来なくなります。 リソースを共有しているような環境下では注意して下さい。
HerokuへNode.jsのアプリをディプロイし、heroku ps
コマンドでwebプロセスの状態をチェックして下さい。
:::term
$ git push heroku master
Counting objects: 30, done.
...
-----> Heroku receiving push
-----> Node.js app detected
...
-----> Launching... done, v3
http://tp-web.herokuapp.com deployed to Heroku
$ heroku ps
=== web: `node app.js`
web.1: up for 40s
ブラウザ上でアプリケーションを開くために、heroku open
コマンドを実行して下さい。
JSON形式のメッセージが、Rubyの書き出しアプリからNode.jsのwebアプリへ、そして最終的に開いているブラウザへ、
リアルタイムにプッシュされていることを確認出来るでしょう。
Node.jsのwebアプリにあるapp.js
内のreadAndSend
ファンクションは、Rubyのデータ書き出しコンポーネントにより、Cappedコレクションに送られたメッセージを
利用する役割を担っています。
:::javascript
function readAndSend(socket, collection) {
collection.find({}, {'tailable': 1, 'sort': [['$natural', 1]]}, function(err, cursor) {
cursor.intervalEach(300, function(err, item) {
if(item != null) {
socket.emit('all', item);
}
});
});
// ...
};
collection.find
の呼び出しは、messages
コレクション内の全ドキュメントに渡る繰り返し処理を行うカーソルを返します。
'tailable'
オプションは、このカーソルへ、もしデータの終端に達しても、追加データを待つよう明示します。
このような仕組みにより、リアルタイムでメッセージを受信する挙動を実装しています。
多種多様なキューのリスニングをデモンストレーションします。
追加のcollection.find
の箇所は、
コンプレックスのメッセージタイプのみ同じ呼び出しを行うことが出来ます。
:::javascript
collection.find({'messagetype':'complex'}, {'tailable': 1, 'sort': [['$natural', 1]]}, function(err, cursor) {
cursor.intervalEach(900, function(err, item) {
if(item != null) {
socket.emit('complex', item);
}
});
});
socket.emit
を使うことで、それぞれのカーソルの繰り返し処理が、メッセージドキュメントを放出していることに注目して下さい。
:::javascript
socket.emit('all', item);
// and
socket.emit('complex', item);
これは、サーバー側から接続された全クライアントのブラウザへ、メッセージドキュメント(JSONオブジェクト)をプッシュしています。 このクライアントプッシュ型の特徴を提供しているライブラリは、Socket.IOと呼ばれています。
Node.jsのwebアプリケーションは、単一のindex.html
ページを提供します。このページは、Socket.IOを使っており、サーバーへのコネクションを開き、
メッセージタイプが‘all’と‘complex’であることを示したリスナーをクリエートします。
:::javascript
var socket = io.connect('/');
socket.on('all', function (data) { ... }
socket.on('complex', function (data) { … }
WebSocketsを使った双方向通信は、現在利用することが出来ません。
実際には、サーバーサイドのSocket.IO設定が 10秒毎のタイムアウトでXHRのポーリングを利用するよう、コネクションに対し強要し、 クライアント側は、追加データをポーリングするようサーバーへ要求します。
:::javascript
io.configure(function () {
io.set("transports", ["xhr-polling"]);
io.set("polling duration", 10);
});
一度、ブラウザが利用可能なメッセージを全て表示し切ると、一端停止します。新たなメッセージがデータベースへ追加されると、 ブラウザへそれらのメッセージがプッシュされ、動作を再開します。
この記事で取り上げた4つのテクノロジーは、コンポーネント化されたリアルタイムアプリをサポートする 多くの組み合わせの1つでしかありません。より重要なことは、MongoDBのデータ管理の柔軟性とCedarの多言語対応が、 モノリシック・アプリケーションを避け、 モジュール化されたシステムデザインを採用する役割を担っていることです。