-
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と呼ばれています。
The Node.js web application serves up a single index.html
page that uses Socket.IO to open a connection to the server and create a listener attached to message types ‘all’ and ‘complex’.
:::javascript
var socket = io.connect('/');
socket.on('all', function (data) { ... }
socket.on('complex', function (data) { … }
True bi-directional messaging with [WebSockets](http://en.wikipedia.org/wiki/WebSocket) is not yet available.
In reality, the client is polling the server for more data as the server-side Socket.IO configuration forces the connection to utilize XHR-polling with a 10-second timeout.
:::javascript
io.configure(function () {
io.set("transports", ["xhr-polling"]);
io.set("polling duration", 10);
});
Once the browser has shown all available messages it will stop. As new messages are inserted into the database, the browser will be pushed the new messages and resume.
The four technologies covered in this article are just one of many combinations that support a componentized real-time app. More fundamental is the role MongoDB's data-flexibility and Cedar's polyglot capabilities play in eschewing monolithic applications for a more modular system design.