Rinda::Ring

Rinda2のRingはTupleSpaceを利用したネームサーバと、ネームサーバをLANに公開するしくみで構成されます。

はじめに

dRubyには標準的なネームサーバがありません。これは意図したものです。 ですが、Ringはネームサーバの一つです。なぜ今になってこれを書いたのでしょう。

それは、もちろんおもしろそうだったからです。 *1

Ringはアプリケーションがダイナミックにサービスを検索する機能を提供します。 アプリケーションはRingのネットワークに接続し自サービスをRingに登録したり、 他のサービスを検索することができます。 ダウンしたサービスを定期的にネームサーバから削除する機能や、 必要なサービスが登録されたことを通知する機能をもっており、 ダイナミックにシステムのプロセス構成を変更していくことが可能です。

Ringはこんなところがお得です。

  • 事前にサービスのURIを知っていなくてもよい
  • 事前にネームサーバのURIを知っていなくてもよい
  • ダウンしたサービスは定期的にネームサーバから削除される
  • サービスがRingに参加した通知を受け取ることができる

Ringは二つの要素から構成されます。

  • TupleSpaceを利用したネームサーバ
  • TupleSpaceをLAN上で検索するためのしくみ

Ringはこういうのには向かないかもしれません。

  • CGIで起動されるプロセス - 毎度ネームサーバを検索、サービスを検索‥が必要です。

Jiniにくわしい方へ

RingはJiniのdiscovery, lookupに似ていますが、 リースに相当する部分の考え方が異なっています。

Ringでは他のプロセスに提供されるオブジェクト一般をリースで管理するのではなく、 TupleSpaceに登録されているエントリ(タプル)に有効期限をつけるだけにとどめています。

タプルの有効期限の更新は登録したサービスの責任となりますが、 サービスに対して定期的にTupleSpaceからコールバックすることができるので、 更新のためのサービス側のコードはとても小さなものとなります。

ネームサーバ

Ringのネームサーバは次のようなアイデアです。

  • オブジェクトの名前、種類、参照をタプルにする
  • オブジェクトの名前を表わすタプルをTupleSpaceにwriteして公開する
  • タプルのパターンマッチングを用い、read/read_allで検索する

http://www2a.biglobe.ne.jp/%7eseki/ruby/ns.jpg

Ringのでネームサーバとして用いるタプルは次の形式です。

[:name, 分類, a DRbObject, 説明文]
  • tuple[0] -- Symbol。ネームサーバのエントリとして使うことを示すシンボル。:name の固定
  • tuple[1] -- Symbol。分類を示す。:name_server, :place, :rwikiとかシステムで決める。
  • tuple[2] -- DRbObject。オブジェクトへの参照
  • tuple[3] -- String。説明文。なにもなければ空文字列あるいはnilを与える。

具体的には次のように使います。

# 600sec有効な名前の登録
tuple = ts.write([:name, :rwiki, book.front, "RWiki2 front"], 600)

# 検索
tuple = ts.read([:name, :rwiki, DRbObject, nil])
rwiki = tuple[2]

RingServer

RingではUDPを使ってネームサーバとなるTupleSpaceの参照を紹介します。

RingServerはこの一連の処理を行うクラスです。 UDPのポートを監視して、受信したDRbObjectにTupleSpaceの参照を渡すします。

もっとも簡単なRingServerの使用例は次のとおりです。

require 'rinda/ring'
require 'rinda/tuplespace'

ts = Rinda::TupleSpace.new
place = Rinda::RingServer.new(ts)
$stdin.gets

RingServerにTupleSpaceを渡して生成するだけでUDPのポートを監視するサーバスレッドが動き出します。 今のところ、サーバスレッドを停止させるインターフェイスはありません。

drb-2.0系のパッケージでは、sample/ring_place.rbがRingServerの使用例です。 RingServerは固定的なUDPのポートを開きますので、マシン(ネットワークインターフェイス)に一つだけ起動できます。 ネットワーク上に複数のRingServerを稼働させてもかまいません。

Ringネットワークの利用者がRingServerを検索する様子を次節RingFingerで説明します。

RingFinger

RingFingerはRingServerを探すユーティリティクラスです。 インスタンスをつくって明示的に探す使い方と、 クラスメソッドを利用する使い方ができます。

RingFingerはLANにUDPでprocの参照をブロードキャストして、 RingServerにコールバックしてもらうことでRingServerを検索します。

http://www2a.biglobe.ne.jp/%7eseki/ruby/ring_lookup.jpg

この図はRingFingerのlookup_ring_anyの動きを示しています。 lookup_ring_anyはlookup_ringとQueueで実装されています。

  1. RingFinger.new.lookup_ring { |ts| queue.push(ts) } … RingFingerにlookup_ringを送ります。 ブロック引数にqueueに積むブロックを与えます。
  2. UDPを使ってブロック引数の参照(DRbObject)をLANにブロードキャストします。
  3. RingServerはUDPのポートを監視していて、受信したDRbObjectにTupleSpaceの参照を渡します。
  4. ブロックはQueueにTupleSpaceの参照をpushし、
  5. Queueをpopしていたスレッドは動き始めます。これでLAN上に存在するRingServerのTupleSpaceのうち 一つが手に入りました。lookup_ring_anyはこれで完了です。
  6. ClientはこのTupleSpaceをネームサーバとして扱い、サービスを登録したり検索して本来の処理をはじめることになります。

RingFingerリファレンス

drb-2.0.1版RingFingerのリファレンスです。

RingFinger.new(broadcast_list=@@broadcast_list, port=Ring_PORT)

ブロードキャスト先やポートを指定してRingFingerを生成します。デフォルトのブロードキャスト先は 次の通りです。

@@broadcast_list = ['localhost', '<broadcast>'] 
RingFinger#lookup_ring(timeout=5, &block)

LAN上のRingServerを検索し、blockにyieldします。 *2

RingFinger#lookup_ring_any(timeout=5)

LAN上のRingServerを検索し、最初に見つかったRingServerを返します。 最初に見つけたRingServeを@primaryインスタンス変数に、 @ringsインスタンス変数に残りのRingServerを保持します。 lookup_ring_anyは、最初のRingServerが見つかった時点で戻ります。 timeout秒以内には@ringsにはまだRingServerが追加されるかもしれない点に注意が必要です。

RingFinger#primary

lookup_ring_anyで最初に見つけたRingServerを返します。

アプリケーションが何度もRingFingerを生成したくない場合、何度も検索したくない場合のために クラスメソッドが用意されています。シングルトン的なものですが、「絶対に一つだけ」のインスタンスに する必要がないのでそういった制御はされていません。

RingFinger.finger

RingFingerの代表的なインスタンスを返します。 初めて呼ばれたとき、loopup_ring_anyでRingServerを検索します。

RingFinger.primary

RingFinger.fingerによって最初に見つけられたRingServerを返します。

RingFinger.to_a

RingFinger.fingerによって2つ目以後に発見されたRingServerを返します。 このメソッド名は適切でないような気がします。変更するかも…。FIXME

アプリケーションの実際

アプリケーションがRingのネットワークに参加する手順、作法、必要なコードについて説明します。

アプリケーションがはじめにやることは、ネットワーク上のRingServerの検索です。 サービスを提供する場合にも利用する場合にも必要な処理です。 以下のスクリプトは sample/ring_echo.rb の抜粋です。

require 'rinda/ring'
require 'drb/drb'
require 'drb/eq'

DRb.start_service

finger = Rinda::RingFinger.new
ts = finger.lookup_ring_any

アプリケーションがサービスを提供する場合、適切なネームサーバのタプルをRingServerのTupleSpaceへwriteします。


*1また、自宅のシステムが複雑で管理がめんどうになったからです
*2LAN上には複数のRingServerが存在するかもしれません。