レベル: 中級 Sing Li (westmakaha@yahoo.com), Author, Wrox Press
2003年 9月 30日 日用品レベルのPCをベースとしたサーバーやネットワークのハードウェアをオープンソースのJavaソフトウェアと組み合わせることで、Webサービスやアプリケーションを効果的に拡大展開することができます。強いインパクトのあるウェブ層クラスタリング・シリーズ第2回の今回は、Sing Liが典型的なクラスタシステムの設計シナリオに飛び込み、「万能」のソリューションが存在し得ないこと、またJavaSpacesやJini技術に基づく適応性に富んだソリューションを使用することで、多様な要求を満足させられることを示します。
アプリケーション・サーバーが存在するJ2EEアーキテクチャーのWeb層では、アプリケーションの状態情報はよく、サーバー側のセッションの形式で保持されます。こうしたセッションを外部化し、ネットワーク化した一群のサーバーに複製することで、Java WebアプリケーションやWebサービスを実行する、スケーラブルで非常に高可用性(highly available)なクラスターを生成することができます。このシリーズ第1回目の記事「JavaGroupsを使用してウェブサービスやアプリケーションをスケーリングする」ではその実現方法として、メモリ内セッション複製の実装にJavaGroupsコミュニケーション・ツールキットを使うものを説明しました。
第2回目の今回は、ネットワーク・サーバー・クラスターにまたがるWebアプリケーションやWebサービスを拡大する手法として、少しだけ違ったものを試みます。今回使用する技術はJavaSpacesですが、これはJini技術ファミリー(参考文献)の一部として提供されているソフトウェア・サービスです。JavaSpacesを使うことで、分散システム一般(特にクラスター化システム)の設計に高位レベルの手法が使えるようになり、設計の複雑さが減少し、また適応性が向上します。JavaSpacesを使って分散共有メモリ・モデルを実装することで、(第1回で行った複製とは異なり)サーバー群にまたがるセッション・データの共有に設計の焦点を絞ってみます。
3つの基本的な操作を備えた分散型システムを設計する
概念的にはJavaSpacesは分散したバッグ(bag)であり、その中にエントリー(サイドバー「Jiniエントリー」参照)と呼ばれるJavaオブジェクトが置かれます。スペースは複数のユーザー(クライアント)が同時に共有することができます。ユーザーは普通、他のネットワーク・ノードまたは独立のJava仮想マシン(JVM)です。Javaオブジェクトが(write操作で)そのスペースに置かれると、そのスペースのユーザーはどれもその内容を(take操作で)スペースから取り出すことにより、または(read操作で)何もせずそのままにすることにより、その内容を読むことができます。
こうした単純な3つの操作(write、take、read)はJavaSpacesの持つ完全な機能範囲を優美にカプセル化するものであり、これらを使って分散化、大規模並列、クラスター化コンピューティング・ネットワークの設定を行うことができます。図1はJavaSpacesが行う操作を説明しています。
図1. JavaSpacesの基本的な操作
図1にあるように、JavaSpacesのテンプレート・マッチング機構(サイドバー「JavaSpacesでのテンプレート・マッチング」を参照)によって、クライアントはスペースから選択的にオブジェクトを読み出し、取り出しできるようになります。
JavaSpacesの本質
 |
Jiniエントリー
Jiniエントリーはnet.core.jini.Entryインターフェースを実装するJavaオブジェクトです。このインターフェースはjava.io.Serializableのサブクラスであるマーカー・インターフェースであり、実装が必要なメソッドはありません。各Jiniエントリーには、Javaオブジェクトであるフィールドがあります。こうしたフィールドはシリアル化もできる必要があります。JavaSpacesを使用する際には、エントリーの各フィールドは独立にシリアル化されます。このエントリーはシリアル化されたオブジェクト・グラフのルートではないので、2つのフィールドが同じオブジェクトを参照している場合には、参照された同じオブジェクトの2つのコピーが別々にシリアル化されます。
|
|
このシリーズ第1回では一群のプロセスを扱いました。つまり(JavaGroupsにあるような)グループ・メンバーシップの追跡、メッセージの調整、送信、受信などです。JavaSpacesを使うと、メッセージやグループ、またメンバーシップのサイズなどを意識することなく分散システムを設計することができます。実際、read、write、take操作だけを使えば、JavaSpaces分散アプリケーションは完全に単一のスレッドで書くことができ、並行性を全く意識する必要がないのです! 言い換えると、JavaSpacesを使ってクラスター化システムを設計する際には、非常に高位の抽象化で作業できるということです。そうした高位レベルで作業する利点として、2つ重要なものを挙げることができます。
- 設計が非常に単純、また理解しやすいものになり、長期的な維持管理に有利になる
- 大幅なコード変更をせずに様々な要求に素早く対応できる
こうした利点は伝統的に複雑な分散アプリケーションのみならず、ほとんどどんなソフトウェア・システムにとっても望ましいものです。もちろん分散システム全体としての複雑さは変わっていません。変わったのは、どこでその複雑さを扱うか、という点です。JavaSpacesを使うことで、より複雑な直交性に関する懸念のほとんど全てがAPIの線(JavaSpacesサービスの具体的な実装)の下に押しやられ、その結果、設計者は実際のアプリケーションの問題に専念できます。つまりこうした複雑な問題は、有能で熟練した技術者や理論家が一度解決しさえすればすむようになった、ということです。このアーキテクチャーをさらに理解するためには、概念的な面だけでなくJavaSpacesの持つ物理的な意味についても見てみる必要があります。
JavaSpacesの応用
物理的には、JavaSpaceはJiniの統合サービスが実装するJavaインターフェースです(「Jiniのすべて」をご覧下さい)。インターフェースであるnet.jini.space.JavaSpaceは、JavaSpacesクライアントがJiniのサービス機能にアクセスするための唯一の手段です。JavaSpacesのサービスはリモート・サービスですが、このサービスがローカルのJiniプロキシー(動作的にはダウンロードしたドライバーと似ています)をアプリケーションに対して用意し、JavaSpacesインターフェースへのコールを全て、アプリケーションのVM内で「ローカル」なものにします。表1は各操作のより詳しい説明です。
表1 基本的なJavaSpaces操作
| 操作 | 説明 |
|---|
read
| 提供されたテンプレートと一致するエントリーをスペース中から検索し、そのエントリーのコピーを返します。エントリーが使用可能になるまで、または規定されたタイムアウトに達するまで、他の操作をブロックします。 |
take
| 供給されたテンプレートと一致するエントリーをスペース中から検索し、スペースからそのエントリーを取り出し、エントリーのコピーを返します。エントリーが使用可能になるまで、または規定されたタイムアウトに達するまで、他の操作をブロックします。 |
write
| エントリーのコピーをスペースに置きます。 |
これらの基本的な操作は、Jiniエントリーと呼ばれるオブジェクトに対して行われます。こうしたエントリーのフィールドとして、Java オブジェクトがどのように付加されるかについては、サイドバー「Jiniエントリー」を参照して下さい。
JavaSpaceインターフェースにはこの3つの基本的なオペレーションの他に、頻繁に使用される3つのメソッドがあります(表2)。
表2. 他のJavaSpaces操作
| 操作 | 説明 |
|---|
notify
| 提供されたテンプレートと一致するエントリーがスペースに書き込まれる場合は常にJiniリモート・イベントを通して通知されるように、interestを登録します。 |
takeIfExists
| 一致した場合のみエントリーを取り込み、それ以外の場合にはnullを返します。この操作はエントリーの一致待ちをブロックしません(確定していないトランザクションによってそうしたエントリーがロックされない限り・・トランザクションについては次のセクションを参照して下さい)。 |
readIfExists
| 一致があった場合のみエントリーを読み込みます。それ以外の場合にはnullを返します。この操作はエントリーの一致待ちをブロックしません(確定していないトランザクションによってそうしたエントリーがロックされない限り・・トランザクションについては次のセクションを参照して下さい)。 |
JavaSpacesでのトランザクション
 |
JavaSpacesでのテンプレート・マッチング
read やtake、readIfExists、takeIfExists それにnotifyメソッドは全て、JavaSpaceのテンプレート・マッチング機構を使って、JavaSpaceのどのエントリーを処理するかを決定します。もっと具体的に言うとテンプレートは単に、部分的にまたは完全に中身の入ったエントリーです。エントリー・タイプは、一致させたいオブジェクト・タイプを反映する必要があります。つまり指定したJavaクラス/インターフェースまたはサブクラスが一致することになります。nullを含むフィールドはどれも、マッチング検出期間中にはワイルドカードとして動作します。中身の入ったフィールドはどれも、シリアル化したバイナリ比較レベルで完全に一致する必要があります。こうした連想一致は大規模並列操作につながり、潜在的にはハードウェア支援での実装にもつながります。JavaSpacesのテンプレート・マッチングについてさらに詳しくは参考文献にある、JSK資料を参照してください。
|
|
JavaSpaces操作はトランザクションの意味合いで行われます。これは次のような手順で行われます。
- トランザクション・マネージャー・サービスを見つけ出す
-
TransactionManagerインターフェースを使ってトランザクションを生成する
- JavaSpaces操作を呼ぶ時にそのトランザクションを提供する
トランザクションのコンテキストで実行される一連の操作には、成功(トランザクションが実行された)と失敗(トランザクションが異常終了した)の2つの結果しかありません。一連の操作の結果は、トランザクションが実行された時に他のJavaSpacesクライアントから見えるのみです。トランザクションが異常終了すると、そのJavaSpaces操作(つまりスペースの内容を変更するwriteやtake操作)の結果は元に戻され、他のクライアントにはその試みが行われたことが分かりません。
地理的に分散した実装を含む複数のJavaSpacesで実行される操作を、一つのトランザクションで組み合わせることができます。表面下でトランザクション・マネージャーが、関係するJavaSpacesサービス・インスタンス間の分散化2フェーズ・コミット・プロトコルを調整するのです。
トランザクションのセマンティックスでは、部分的な失敗モードを概念的に無くしてしまうことで、その処理を大幅に簡略化しています。ここではスペースAからのエントリーmのtakeとスペースBへのwriteを組み合わせることで、アトミック転送操作を生成しました。転送中に何かが失敗した場合には、エントリーmはスペースAから除かれることはありません。転送が成功すれば確実に、mがスペースBにあり、かつスペースAからは除かれたことが分かるのです。
JavaSpacesの概念がしっかりと頭に入ったので、いよいよこれをWeb層クラスタリングの問題に応用することができます。前回の記事では、一群の分散サーブレット・コンテナにまたがったアプリケーション・セッションをメモリ内に複製することで、スケーラブルで高可用性なクラスターが構築できることを説明しました。JavaSpacesを使うと、実際のセッション複製をどのように扱うかについての詳細を厳密に見なくてもすむようになります。その代わりに、セッション共有のセマンティックスを高レベルから注目するのです。
セッション共有に分散共有メモリを使う
JavaSpacesを使うと、ネットワークにまたがる共有メモリ・システムに似たソフトウェアを生成できます。この概念により、そのスペースの全クライアントがセッション情報を共有することができるようになるのです。図2はセッション共有を説明しています。
図2. 分散共有メモリを使用してセッション共有を実装する
図2で、アプリケーション・セッション情報は分散共有メモリに保持されます。アプリケーション・サーバーの一つが共有セッションに加える変更は、全てのサーバー・インスタンスから見ることができます。全てのサーバーは、共有セッション情報を同じ(ネットワーク化された共有の)メモリ位置から読み取ります。
フェールオーバーのためのマスター/スタンバイ構成をサポートする
ではJavaSpacesを使って分散共有メモリのセマンティックスを作ってみましょう。まず最初のシナリオでは、トランザクション内部でtake操作とwrite操作を組み合わせ、update操作を作ります。リスト1はソース・ディストリビューションにある、実際のJavaコードです。
リスト1. updateSession()メソッド
public boolean updateSession(Serializable sess) {
if ((spaceService == null) || (transactionService == null))
return false;
Transaction transact = null;
try {
Transaction.Created tc =
TransactionFactory.create(transactionService,
TRANSACTION_LEASE);
transact = tc.transaction;
} catch (Exception ex) {
ex.printStackTrace();
return false;
}
try {
SessionEntry tmpEntry = (SessionEntry) spaceService.take(
new SessionEntry(SESSION_KEY, null),
transact, JavaSpace.NO_WAIT);
if (tmpEntry == null) {
transact.abort();
return false;
}
spaceService.write(new SessionEntry(SESSION_KEY, sess),
transact, LONG_LEASE);
transact.commit();
} catch (Exception ex) {
ex.printStackTrace();
try {
transact.abort(); }
catch (Exception ex2) {
ex2.printStackTrace();
}
return false;
}
return true;
}
|
このコードでは、トランザクションがtakeとwriteの組み合わせをアトミックにしています(サイドバー「なぜトランザクションを使うのか」に説明があります)。注意して欲しいのですが、write操作はスペースから取り出したデータに対しては操作せず、JSCartアプリケーション(アプリケーションが保持するセッション・イメージ)から渡される、シリアル化可能(serializable)オブジェクトのイメージを書き込むのです。
この設計はマスター/スタンバイ構成でフェールオーバー機能を使うシステムで有用です。この構成ではマスター・サーバーが入ってくるリクエストを全て受け取り、スタンバイ・サーバーはマスター・サーバーがクラッシュしたり、失敗したりした時にのみ切り替えて使われます。図3はこのシナリオを説明しています。
図3. フェールオーバー構成
 |
なぜトランザクションを使うのか
take操作で取り出されてしまったエントリーは他のユーザーにはもはや使用できないので、更新のtakeやwrite操作の前後にはトランザクションが必要無いと最初は思えるかも知れません。ところがトランザクションの主な目的はエントリーを同時アクセスから保護することではなく、(takeの後でかつwriteの前に)サーバーのクラッシュが起きてセッションが消滅してしまうのを防ぐことなのです。トランザクションを使うことで、そのトランザクションへのリース期限が切れればセッション・エントリーがトランザクション前の値に復元されることが保証されるのです。
|
|
図3のスタンバイ・サーバーは通常、入ってくるリクエストを処理しないので、このサーバーが共有セッション情報にアクセスするのは次の2つの場合のみです。
- 起動時に、その時のセッション状態を取得するため
- マスター・サーバーがクラッシュした時に、直近のセッション状態を取得するため
ただしステージ1と2の間には、スタンバイ・サーバーは共有メモリからの読み込みを行う必要は何もありません。実際スタンバイ・サーバーはステージ2で共有メモリを読むまでセッション変化には関知しないので、スタンバイ・サーバーがステージ1と2の間に保持しているセッション情報は古い可能性があります。
フェールオーバーをサポートするセッション共有クラスターの実装には、このような疎結合の構成で十分です。現実的には、クラスタリング・アプリケーションにはそれぞれ固有の要求があるかも知れません。上のソリューションは効率的ではありますが、そうした要求には適切に対応できないかも知れません。JavaSpacesを使ってクラスター化ソリューションを実装することにより、最小限の設計変更、コード変更で、多様な設計要求に対応できるようになります。この機敏性を具体的に理解するために、フェールオーバーに加えて負荷分散もサポートする、より密結合のソリューションをどのように構築すればよいかを見て行きます。
負荷分散クラスタリングでスケールアウトする
使用可能なリソースを最大限利用するために一部のクラスター化アプリケーションでは、入ってくるリクエスト処理のために、(先のシナリオでの)スタンバイ・サーバーを要求する場合があります。この構成の主な利点は、(1台以上のスタンバイ・サーバーがある場合には)リクエスト処理がクラスター中の何台かのサーバーに分散する結果、より大きな同時使用のユーザー・ベースに合わせてアプリケーションを規模拡大(スケールアウト(scaling out)と呼ばれます)できることです。この構成は一般的に負荷分散クラスター構成(load-balanced cluster configuration)として知られています。実装によっては入ってくるリクエストを最も負荷の小さいサーバーに振り分け、またある実装ではラウンドロビン式のリクエスト分散を使います。図4はこの概念を説明しています。
図4. 負荷分散クラスター
負荷分散クラスターでは、フェールオーバーは自動的に行われます。どれか一台のサーバーに障害が発生すると、入ってくる新しいリクエストは全て、残っているサーバーに分散されます。
負荷分散クラスターでは、先のシナリオにあった共有メモリ実装を使うことはできません。これは疎結合ソリューションでは、セッション情報の破損が起こり得るためです。
このシナリオでは、ある特定の共有セッションを変更するのはマスター・サーバーのみだと想定することはできません。ローカルに保持されたセッション情報は、どんな瞬間にも、古く同期していないものとなり得るのです。この問題を解決するには、共有セッションデータに対するread-modify-write操作がアトミックであることを保証する必要があります。
アトミック・セッションread-modify-write
セッションへのリクエストを、クラスター中のどのサーバーでも、いつでも処理できるように保証するには、セッション・データの維持管理に大きな制約を課す必要があります。つまりサーバーは変更をかけるための元データとして、ローカルに保存された共有セッションのイメージを使ってはいけないのです。これはローカル保存のイメージは古く、これを使うと障害につながりかねないためです。ですからサーバーはセッション・データに変更を加える前に、そのセッション・データを共有メモリから読み取る必要があります。
ここでは第1回の記事でのJavaGroupsショッピングカート・アプリケーションを更新して、JavaSpacesネットワーク共有メモリに使いました(ダウンロードについては参考文献にあります)。このアプリケーションは一つのショッピングカート・アプリケーション・セッションの内部動作を説明しています。表3に示すように、2つのクラスにのみコード変更が行われています。
表3. JSCartアプリケーションで大きな変更のあったクラス
| クラス名 | 説明 |
|---|
JSCart
| これは中心となるGUIアプリケーションで、以前はJGCartだったものです。このアプリケーションでは、セッションデータを読んだり変更したりする時には、セッションデータのコピーを独自に保持するのではなくCartSessionManagerクラスを使用します。 |
CartSessionManager
| このクラスはJSCartに共有セッション機能を提供し、全てのJavaSpaces操作や処理をカプセル化します。 |
JSCartとCartSessionManagerの間の作業区分は明確です。JSCartはGUIを維持し、必要なセッション・データについては全てCartSessionManagerに任せます。CartSessionManagerはそのAPIを、JavaSpacesのネットワーク共有メモリを使って(クライアントには気付かれずに)実装します。これはつまり、CartSessionManagerにはセッション内でデータがどのように含まれているかが分からない、ということです。セッションの詳細はCartSessionManagerには分からないのです。そのため次のような理由から、アトミックなread-modify-writeサイクルの実装には細工が要ることになります。
- 共有メモリをどのように読み書きするかを知っているのは
CartSessionManagerのみ
- 意味のある変更をセッションに加えるにはどうすべきかを知っているのは
JSCartのみ
ここのコードでは、com.ibm.devworks.javaspace.PrepReadModifyWriteと呼ばれるインターフェースを使うことでread-modify-writeを実装しています。リスト2は、このインターフェースをどのように定義するかを示しています。
リスト2. PrepReadModifyWriteインターフェース
public interface PrepReadModifyWrite {
public void afterRead(Object readSession, Object token);
}
|
このインターフェースには、afterRead()と呼ばれる一つのメソッドしかありません。
JSCartがread-modify-write操作を実行する必要がある時には、JSCartはCartSessionManagerオブジェクトのreadModifyWrite()メソッドを呼び、PrepReadModifyWriteインターフェースを実装するオブジェクト(ここではJSCartクラス自体)を渡します。
ユーザーがGUIを使ってカートに品物を載せたときにJSCartがこのコールを呼びます。これはitemOrdered()メソッドで処理されます(リスト3)。
リスト3. itemOrdered()メソッドでのread-modify-write
public void itemOrdered(OrderEvent ev) {
System.out.println("JSCart received ordered event - " + ev);
boolean success = sessionMgr.readModifyWrite(this,
(Object) new OrderEvent(ev.getDesc(), ev.getPrice())) ;
}
|
これによってCartSessionManagerがトランザクション内部で、次をアトミックに行えるようになります。
- JavaSpaces の
take操作を実行する
-
PrepReadModifyWriteインターフェースのafterRead()メソッドを使い、見えないセッションを変更するためにJSCartに戻す
- 変更されたデータでJavaSpacesの
write操作を実行する
分散共有メモリでJSCartをテストする
 |
スペースをシードする必要性
JavaSpacesの現在の仕様では、 n台のクライアント群が信頼性のある方法で協力し、共有のシングルトン・エントリーをスペースに「シードする」手段として一般的に知られたものはありません。これが、SessionSeederクラスが実装するセッション・シーディング・ユーティリティの理由です。このユーティリティは、他のクライアントが起動する前に初期セッション・エントリーをスペースに書き込みます。これは概念的に考えると、ネットワーク化メモリを初期化して、領域を共有したりマップしたりできるようにすることに似ています。
|
|
このバージョンのJSCartを次のような3つのステップで試してみます。
-
code\jini2ディレクトリでJiniとJavaSpaces環境を起動します。code\jini2ディレクトリから、startupをタイプします(システムのセットアップについては各ディレクトリにあるREADME.TXTの指示に従ってください)。
-
scriptsディレクトリにあるseedspc.batバッチファイルを使い、JavaSpaceへのセッション・エントリーを書くことでセッション・シーディング・ユーティリティを実行します。コード・ディレクトリからscripts\seedspcをタイプします(スペースのシーディングについてはサイドバー「スペースをシードする必要性」を見てください)。
-
scriptsディレクトリでruncart.batバッチファイルを実行し、2つまたはそれ以上のJSCartインスタンスを実行します。codeディレクトリからscripts\runcartをタイプします。
ソースコードからバイナリをビルドする必要がある場合には、code\srcディレクトリにあるcompile.batバッチファイルを使います。
簡単かつ視覚的に分かりやすいように、JSCartは一つの共有セッションに対してのみ動作します。現実の設計の場では多くのセッション・エントリーがあり、各セッションを独立に変更する必要があるでしょう。また全セッションのIDを保持するために、別の共有エントリーも必要になるでしょう。
では最初のカートに品物を載せてみましょう。これは最初のサーバーにadd-itemリクエストが入ってきたことに相当します。これは図5に似たものになりますが、2台のカートは同期していないように見えることに注意してください。
図5. Visual JavaSpacesカートが同期していないように見える
ところが同じセッションに対するリクエストを2番目のサーバー/カートで(2番目のカートに商品を加えることにより)シミュレートすると、2番目のカートが即座に、最初のカートにある品物に「追いつく」ことが分かります。同じセッションに対して入ってくるリクエストが何台ものサーバーに分散されるのをシミュレートするために、何台かのカート間で商品の追加を何度か行ってみると、共有のセッション情報は一定のまま保持されることが分かるでしょう。
このセッション共有の手法はフェールオーバー機能を持ち、負荷分散クラスター・サーバーには適切ですが、私たちがここで必要とするものには十分ではありません。ショッピングカートは実際、共有メモリへの書き込みが起きた時には必ず、視覚的GUIを更新できるように通知を受ける必要があるのです。この要求は大部分のクラスター・システムに課される要求よりもずっと厳しいかも知れません。またその解決手段も、共有メモリへの書き込みの度毎にネットワーク全体に渡って通知の嵐が引き起こされるため、さらに帯域幅を必要とするものになります。それでもなお、JavaSpacesは再び課題に対応し、様々な分散アプリケーションの様々な要求に適応するのです。
セッション状態変化通知にリモート・イベントを使う
JSCartはどんな時にも、共有セッションの状態を視覚的に反映する必要があります。この特別な要求を満足させるために、JSCartインスタンスはセッション状態に変化が起きた場合には、必ず通知を受ける必要があります。つまり、インスタンスのどれかで共有メモリへの書き込みが行われた場合には、JSCartインスタンスは必ず通知を受ける必要があるということです。
CartSessionManagerクラスは特にこの目的のためにaddDistributedWriteListener()メソッドを提供します。JSCartはこのメソッドを呼び、自分をリスナーとして登録します。JSCartはリスト4に詳細を示したcom.ibm.devworks.javaspace.DistributedWriteListenerインターフェースを実装します。
リスト4. セッション変化通知のためのDistributedWriteListenerインターフェース
public interface DistributedWriteListener extends java.util.EventListener {
public void sessionChanged(Object session);
}
|
CartSessionManagerは、共有セッション情報が変更された時には必ずsessionChanged()メソッドを呼ぶことを保証します。これによってJSCartがそのGUIを最新のセッションデータで更新できるようになります。
内部的には、CartSessionManagerはJavaSpacesのnotify操作を使ってイベント通知を実装しています。JavaSpacesは特定なテンプレートに一致するエントリーがJavaSpacesに書き込まれた時には、Jiniのリモート・イベント通知機構(参考文献)を使って、リスンしているクライアントに対してリモート・メソッド呼び出し(remote method invocation: RMI)コールを行います。これが起きる前に、クライアントはJavaSpacesのnotify()メソッドを使ってリモート・リスナーを登録する必要があります。このコードはCartSessionManagerクラスのregisterRemoteEvent()メソッドにあります(リスト5)。
リスト5. JavaSpacesのリモート通知を登録するためのregisterRemoteEvent()メソッド
private boolean registerRemoteEvent(Configuration config) {
if ((spaceService == null) || (transactionService == null))
return false;
EventRegistration evtReg;
leaseMgr = new LeaseRenewalManager();
try {
Exporter exp = (Exporter) config.getEntry(
CONFIG_COMP,
"serverExporter", Exporter.class,
new net.jini.jeri.BasicJeriExporter(
net.jini.jeri.tcp.TcpServerEndpoint.getInstance(0),
new net.jini.jeri.BasicILFactory(),
false, true));
evtReg =
spaceService.notify( new SessionEntry(SESSION_KEY, null),
null, (RemoteEventListener)
exp.export(this),
/* LeaseTime is 3 minutes */ 3 * 60 * 1000 ,
null);
leaseMgr.renewFor(evtReg.getLease(),
Lease.FOREVER, 3 * 60 * 1000, null);
} catch (Exception ex) {
ex.printStackTrace();
return false;
}
return true;
}
|
 |
リースと自己回復、長寿命ネットワーク
Jiniは期間制限付きのリース(limited duration leases)を広範囲に使用し、自己回復、長寿命ネットワーク(self-healing, long-lived network)の概念をサポートしています。クライアントの代行としてネットワーク化リソース(メモリのような物理的リソースまたはトランザクションのような概念的なリソース)が要求または保持された時には必ず、リソース・ホルダーがリースを許可します。例えば、トランザクション・マネージャーが生成したトランザクションはリースされます。これによって、そのトランザクションを生成したJiniクライアントがクラッシュして再度ネットワークに加わることがなくても、やがてリソースが確実に再リースされるようになります。許可されたリース期間を超えてリソースを確保したいとするJiniクライアントは、期限が切れる前にリースを更新する必要があります。Jiniでのリースについての詳細は、参考文献にあるJSKの資料を見てください。
|
|
リスト5ではJavaSpacesのLeaseRenewalManager ヘルパー・クラスを使ってイベントへのリース更新を続けていることに注意してください。リモート・イベント登録はJiniにリースされますが、これは各登録が一定のネットワーク化リソースを占有するためです。LeaseRenewalManagerヘルパー・クラスにリース更新を任せることで、このJSCart インスタンスが実行している限りリースの期限が切れることを心配する必要がありません。もう一つの注意点としては、我々のオブジェクトがRMIでアクセスできるように、オブジェクト・エクスポーターにexport()メソッドを使っているという点です。RMIに関するJini 2.0での改善では、明示的にリモート・オブジェクトのエクスポートを要求します。エクスポーター・オブジェクトからサブクラス化する場合には、自動的なスタブ挿入はもう起こりません。
図6はイベント登録とリモート通知のシーケンスで、共有セッション状態変化の通知がどのように実装されるかを示しています。
図6. イベント登録とリモート通知のシーケンス
分散通知を使用可能にした状態でJSCartを試すためには、JSCartとCartSessionManager クラスのソースコードをよく調べ、明示的にマークの付いたコード部分のコメントを注意深く外します。code\src ディレクトリにあるrcompile.batファイルを使って再コンパイルします。これによって必要なRMIスタブも生成され、jscart-dl.jarファイルが生成されます。詳しくはソースコードにあるREADME.TXTファイルを見てください。
まとめ
JavaSpacesとJini技術を使うことで、クラスター・システムを高位レベルで設計することができます。ここでは3つの基本的な操作、read、take、writeを使うことで、クラスター内でアプリケーション・サーバー・セッションを共有するための、分散共有メモリ・モデルを構成しました。JavaSpacesがトランザクションやリースをサポートしていることで、部分的な障害をうまく逃げる設計ができ、セッション共有機構に集中することができます。
分散アプリケーションにはどれも固有の要求事項があり、全てのアプリケーションを汎用的に満足させるようなAPI層を作ることは(不可能ではなくても)非常に困難です。ここでは、マスター/スタンバイ構成でフェールオーバー・セッション共有クラスターと、完全に負荷分散されたセッション共有構成で、どのように要求が異なるかを見てきました。JavaSpacesの持つ高位の適応性のおかげで、こうしたアプリケーションを満足させるソリューションを、最小のコード変更で構成できるのです。我々のショッピングカートが正しく動作するには実際、分散セッション書き込み通知を要求します。space-writeリモート通知をサポートするために、ここでもJavaSpacesが登場します。このカスタムのソリューションは、再設計無しに最小のコード変更のみで実装されています。
図7は設計手法の可能性について、最上位を概念的な手法、最下位を物理的な手法とした連続的なスペクトル分布を示したものです。
図7. 使用可能なクラスター技術として、概念的なものから物理的ものまでのスペクトル
スペクトルの最上位では、手元にある問題に近いオブジェクトやコンポーネントに対して作業すれば良いので、ソリューション設計は非常に容易になります。例えば「セッション」オブジェクトをベースとして作業することもできるのです。スペクトルの最下位には、実装の詳細が生の形のままです(たとえば「このパケットをネットワークに送る」など)。低位のレベルで作業した方が細かな対応が可能で柔軟性も得られますが、エンジニアがより多くの設計、コーディング、テストをする必要も出てきます。高位レベルで作業すれば、カスタムのソリューションを素早く構築でき、実際の問題に集中することができますが、同時に、下層での正しい実装に大きく左右されることにもなります。
クラスタ化ソリューションの設計者は必要な技術を、このスペクトルのどこからでも入手することができます。ご覧の通り、JavaSpacesでのソリューションは非常に高位にあり、前回説明したJavaGroupsによるソリューションはそれよりもやや下に位置します。このシリーズの最終回である次回では、概念スペクトル上でJavaSpacesよりもさらに高位に位置する、最近現れてきた素晴らしい分散システム構成技術を詳細に見て行きます。この技術によってハイインパクトWeb層クラスタリング・ソリューションが、より多くをより少ないコードで設計できるのです。
ダウンロード | ファイル名 | サイズ | ダウンロード形式 |
|---|
| j-cluster2.zip | | HTTP |
参考文献
著者について  | 
|  | Sing LiはWrox Pressから出版されている多数の本の著者で、Professional Apache Tomcat 、Early Adopter JXTA 、Professional Jini などを執筆しています。技術雑誌に頻繁に寄稿しており、P2P発展に関する熱心なエバンジェリスト(伝道者)でもあります。コンサルタント兼ライターであるSingの連絡先はwestmakaha@yahoo.comです。 |
記事の評価
|