Hatena::Grouprubyist

Going My Ruby Way このページをアンテナに追加 RSSフィード

Ruby ロゴ (C) Ruby Association LLC

2010年11月02日(火)

Rinda についてのメモ

| 22:10 |  Rinda についてのメモ - Going My Ruby Way を含むブックマーク はてなブックマーク -  Rinda についてのメモ - Going My Ruby Way  Rinda についてのメモ - Going My Ruby Way のブックマークコメント

他の日記に書いたメモをここに引っ越し。

Ruby の分散環境ライブラリ dRuby の Rinda というモジュールについて。

---

Rinda は Ruby 版の Linda と呼べるものです。

Linda は タプルスペースを基にした分散システムのモデルです。


タプルスペースはタプルを置く「場」のことです。

タプルはデータの「組」のことです。

タプルの操作には以下があります。(Rinda には eval 操作はありません)

out
タプルをタプルスペースに投入する
in
タプルをタプルスケープから取得する(タプルはタプルスペースから取り除かれる)
rd
タプルをタプルスケープから取得する(タプルはタプルスペースに残る)
inp
in の ノンブロック版
rdp
rd の ノンブロック版
eval
(よく分かりません。タプルを評価してプロセスを起動する..らしい??)

Rinda ではタプルスペースは TupleSpace クラスです。

試してみます。

$ irb -r rinda/tuplespace
>> ts = Rinda::TupleSpace.new
=> #<Rinda::TupleSpace:0x8f488f8 @mon_owner=nil, ... (略)</pp>

Rinda ではタプルは Array で表現されます。(最近は Hash での表現も(実験的に?)取り入れられています)

out 操作は write メソッドで行ないます。

>> ts.write([:test, 1, 2, 3, 'hello'])
=> #<Rinda::TupleEntry:0x8ecd73c .... (略)</pp>

rd 操作は、read メソッドで行ないます。

>> ts.read([:test, 1, 2, 3, 'hello'])
=> [:test, 1, 2, 3, 'hello']

read の第 1 引数指定しているのはタプルのパターンです。

パターンマッチしたタプルが in できます。

パターンマッチは以下の条件を満たすと成功します。

  1. 配列の size が一致
  2. 配列の各要素を === で比較して true になる
  3. ただし、nil はワイルドカードとして常にマッチする
>> ts.read([:test, nil, Integer, (3..4), /^he/])
=> [:test, 1, 2, 3, 'hello']

in 操作は take メソッドで行ないます。引数の意味は read と同じです。

in 操作なのでメソッドが成功するとタプルはタプルスペースより取り除かれます。

>> ts.take([:test, nil, Integer, (3..4), /^he/])
=> [:test, 1, 2, 3, 'hello']

read, take メソッドの第 2 引数は結果を待つ時間を秒で指定します。

デフォルトは nil でありタイムアウトしません。タプルが見つからない場合ブロックします。

>> ts.read([:test, nil, Integer, (3..4), /^he/], nil)

第 2 引数を 0 にすると即時リターンします。

これにより inp/rdp 操作が実現できます。

>> ts.read([:test, nil, Integer, (3..4), /^he/], 0)
=> Rinda::RequestExpiredError: Rinda::RequestExpiredError
             :

タプルが見つからずタイムアウトした場合、Rinda::RequestExpiredError 例外が上がります。

TupleSpace#read_all というメソッドもあります。

パターンにマッチする tuple をすべて read して配列で返します。

>> ts.write([:dummy,10])
=> #<Rinda::TupleEntry:0x9a3c3... (略)</pp>
>> ts.write([:dummy,11])
=> #<Rinda::TupleEntry:0x9a3c... (略)</pp>
>> ts.read_all([:dummy, Integer])
=> :dummy, 10], [:dummy, 11?

マッチするパターンが無い場合は空の配列を返します。

>> read_all([:dummy, String])
=> []

---

Rinda のタプルの寿命についてです。

TupleSpace#write の第 2 引数にタプルの寿命を秒数で指定できます。

$ irb -r rinda/tuplespace
>> ts = Rinda::TupleSpace.new
=> #<Rinda::TupleSpace:0x...</pp>
>> ts.write([1,2,3], 10)  #  10 秒を指定
=> #<Rinda::TupleEntry:0x...</pp>
>> ts.read([1,2,3])
=> [1, 2, 3]
(10数秒経過...)
>> ts.read([1,2,3])  #  ブロック

TupleSpace#write の第 2 引数には renewer を指定することもできます。

renewer は renew メソッドを呼ばれた時に延長する寿命を秒数で返すオブジェクトです。(true を返した場合は寿命延長が無用するであることを示す)

Rinda には SimpleRenewer という renewer が用意されてます。

寿命を延長する秒数はコンストラクタの引数で指定します。(デフォルト 180秒)

>> renewer = Rinda::SimpleRenewer.new(200)
=> #<Rinda::SimpleRenewer:0x93985f8 @sec=200>
>> renewer.renew
=> 200

renew メソッドを呼ぶのは TupleSpace です。

renewer の renew メソッドの呼び出しに失敗すると タプルは消されます。

呼ぶ間隔は TupleSpace のコンストラクタの引数で指定します。(デフォルト 60秒)

この間隔を短かくする場合はパフォーマンスに注意です。

>> ts = Rinda::TupleSpace.new(30) # 30秒を指定
=> #<Rinda::TupleSpace:0x...</pp>
>> renewer = Rinda::SimpleRenewer.new
=> #<Rinda::SimpleRenewer:...</pp>
>> ts.write([:abc], renewer)

タプルが dRuby の分散オブジェクトの場合、提供元プロセスが消滅するとタプルの参照ができません。renewer のメカニズムにより、そのようなタプルを除去することができます。

TupleSpace#write は TupleEntry を返します。

TupleEntry#cancel によりタプルを削除できます。

>> ent = ts.write([1,2,3])
=> #<Rinda::TupleEntry:0x..</pp>
>> ts.read([1,2,3])
=> [1, 2, 3]
>> ent.cancel    # タプルを削除
=> true
>> ts.read([1,2,3])  # ブロック

TupleSpaceProxy というクラスがあります。

これは TupleScape のプロキシです。リモートの TupleSpace を取得した場合に使います。

TupleSpaceProxy では タプル操作の際の例外発生でタプルが消滅してしまうのを防ぎます。

タプル操作のメソッドは TupleSpace と同じです。

>> tuplespace = DRbObject.new_with_uri('druby://......')
=> .....
>> ts = Rinda::TupleSpaceProxy.new(tuplespace)

---

Ring は Rinda のネーミングサービスです。

Ring サーバを起動してみます。

Ring はサービスの実現にタプルスペースを必要とします。

リモートでサービスが受けられるように DRb.start_service します。

$ irb -r rinda/tuplespace -r rinda/ring
>> DRb.start_service
=> #<DRb::DRbServer:...</pp>
>> ts = Rinda::TupleSpace.new
=> #<Rinda::TupleSpace:...</pp>
>> server = Rinda::RingServer.new(ts)
=> Rinda::RingServer:...

別のターミナルでサービスを公開してみます。

RingProvider のコンストラクタの引数には、サービスの名前(Symbol)、front オブジェクト、その説明(String) を指定します。(4番目の引数に renewer を指定できますが省略してます)

$ irb -r rinda/ring
>> DRb.start_service
=> #<DRb::DRbServer:...</pp>
>> provider = Rinda::RingProvider.new(:test, [], '')
=> #<Rinda::RingProvider:...</pp>
>> provider.provide
=> #<DRb::DRbObject:...</pp>

さらに別のターミナルでサービスを検索してみます。

$ irb -r rinda/ring
>> DRb.start_service
=> #<DRb::DRbServer:..</pp>
>> ts = Rinda::RingFinger.primary
=> #<DRb::DRbObject:...</pp>

Ring のタプルスペースが取得できました。

ネーミング情報は以下のようなタプルになっています。

[ :name, サービスの名前(Symbol), front オブジェクト, その説明(String) ]

最初の要素の「:name」は固定です。他は RingProvider のコンストラクタで指定したものです。

タプル操作で取得します。

>> tuple = ts.read([:name, :test, nil, nil])
=> [:name, :test, [], ""]

取れました。




参考情報


参考書籍

dRubyによる分散・Webプログラミング

dRubyによる分散・Webプログラミング

dRubyによる分散オブジェクトプログラミング

dRubyによる分散オブジェクトプログラミング

lnznt が実際に参考にしたのは2001年版。2005年版はその改訂版で Rinda についての解説が増えている。

dRuby についてのメモ

| 08:34 |  dRuby についてのメモ - Going My Ruby Way を含むブックマーク はてなブックマーク -  dRuby についてのメモ - Going My Ruby Way  dRuby についてのメモ - Going My Ruby Way のブックマークコメント

他の日記に書いたメモをここに引っ越し。

dRuby についてのメモ

---

通常、front オブジェクトをサービスするプロセスが分散システムに 1 つは必要になります。

require 'drb'

URI   = 'druby://:12345'          # (1)
front = []                        # (2)
DRb.start_service(URI, front)     # (3)
sleep                             # (4)
  1. リモートからのメッセージを受け付ける TCP ポートを指定
  2. front オブジェクトを指定
  3. DRbServer を作成してスレッドで走らせる
  4. main スレッドと一緒に DRbServer スレッドが終了しないようにしている

DRbServer はリモートからのメッセージをローカルのレシーバ(分散オブジェクト)に転送します。

内部的に、分散オブジェクトは「URI + 識別子」で識別されます。

front オブジェクトは識別子がない(URI のみで特定される)特別な分散オブジェクトです。

front オブジェクトへメッセージ送信するには DRbObject を使います。

require 'drb'

DRb.start_service                       # (1)
URI = 'druby://:12345'                  # (2)
ro = DRbObject.new_with_uri(URI)        # (3)
  1. DRbServer スレッドを起動
  2. front オブジェクトを特定する URI を指定
  3. DRbObject を new する

ro へメッセージ送信することで、リモートの front オブジェクトへメッセージ送信ができます。

DRbObject のリモートオブジェクトへメッセージ転送の規則は以下です。

  • DRbObject で定義されているメソッドは転送されない
  • DRbObject で定義されてないメソッドは転送される

分散オブジェクトの識別子は DRbObject を new する際に内部で決定されます。DRbObject.new_with_uri で new した場合は識別子 nil になります。(つまり、front オブジェクトを参照しています)

上の例で、ro[0] とした場合の流れを追います。

  1. DRbObject には [] メソッドが定義されてないので、引数 0 とともにメッセージ転送される
  2. メッセージは、DRbObject.new_with_uri で指定した URI を LISTEN している DRbServer に届く
  3. DRbServer は識別子をもとにレシーバを決定しようとする
  4. 識別子が nil なので、front オブジェクトへ引数 0 とともにメッセージ転送される
  5. front オブジェクトの [] メソッドが引数 0 とともに呼び出される
  6. 戻り値は、(メッセージ転送の順路を逆戻りして) ro[0] の戻り値となる

この時、引数も戻り値も分散オブジェクトとなっていることに注意です。

分散オブジェクトの渡され方ですが、値渡しと参照渡しがあります。

値渡し

オブジェクトのコピーが渡されます。コピーなので、それぞれのエンドでの変更は互いに影響しません。 受け手の知らないクラスのオブジェクトは DRbUnknown として渡ります。

参照渡し

DRbObject が渡されます。リモートメッセージ転送するので変更はリモートにも影響します。 メッセージ転送時、DRbServer がダウンしていると例外が発生します。

値渡しとなるか参照渡しとなるかは以下の規則に拠ります。

  • Marshal.dump できるオブジェクトは値渡しとなる
  • Marshal.dump できないオブジェクトは参照渡しとなる
  • DRbUndumped の Mix-in により、Marshal.dump できないようにする(参照渡しとする)ことができる

上の例では、引数 0 も戻り値の nilMarshal.dump できるので値渡です。

以下のような場合は、参照渡しになります。

(front オブジェクトのサービス側で)
front[0] = $stdout     # IO は Marshal.dump できない

(front オブジェクトの受け手側で)
p ro[0].class          # => DRbObject

以下のような場合も、参照渡しになります。

(front オブジェクトの受け手側で)
ro[1] = $stdout

流れを追います。

  1. DRbObject には []= メソッドが定義されてないので、引数 $stdout とともにメッセージ転送される
  2. $stdoutMarshal.dump できないので、DRbObject が new される
  3. メッセージは、DRbObject.new_with_uri で指定した URI を LISTEN している DRbServer に届く
  4. DRbServer は識別子をもとにレシーバを決定しようとする
  5. 識別子が nil なので、front オブジェクトへ引数の DRbObject とともにメッセージ転送される
  6. front オブジェクトの []= メソッドが引数 DRbObjet とともに呼び出される
  7. 戻り値は、(メッセージ転送の順路を逆戻りして) ro[1] = $stdout の戻り値となる

この後、

(front オブジェクトのサービス側で)
front[1].puts('hello')

などを行うと、front[1] の DRbObject がリモートへメッセージ転送を行ないます。

補足。

DRb について。

  • DRb.start_service(uri, front) の uri, front は省略できます。省略値はいずれも nil です
  • DRb.start_service で与える URI の書式は「druby://host:port」です。

host は省略できます。省略値は "0.0.0.0" です。

port を "0" にした場合は適当なポートが選ばれます。

  • DRb.start_service は複数回呼べます
  • DRb.start_service の戻り値は DRbServer です
  • DRb.theread で(最新の) DRbServer スレッドが参照できます
  • DRb.uri で DRbServer がサービスしている URI が参照できます
  • DRb.front で front オブジェクトが参照できます
  • DRbServer スレッドの Thread#stop_service でスレッドが kill できます

DRbObject について。

  • DRbObject.new_with_uri(uri) は DRbObject.new(nil, uri) と等価です
  • DRbObject.new(obj, uri) で陽に DRbObject が new できます。この時 uri はローカルの DRbServer のものを指定します
  • DRbObject.new で指定する uri の書式は「druby://host:port」です。

host は省略できます。省略値は "127.0.0.1" です


値渡しと参照渡しについて。

(Marshal.dump できないオブジェクトを参照してなければ) Array, Hash などもそうです

Marshal.dump できないオブジェクトを参照しているオブジェクトは Marshal.dump できません


DRbUndumped を Mix-in するには?

class Foo
  include DRbUndumped
end


bar = Bar.new
bar.extend(DRbUndumped)

以上です。



参考情報



参考書籍

dRubyによる分散・Webプログラミング

dRubyによる分散・Webプログラミング

dRubyによる分散オブジェクトプログラミング

dRubyによる分散オブジェクトプログラミング

lnznt が実際に参考にしたのは2001年版。2005年版はその改訂版で Rinda についての解説が増えている。