目次


Java のマルチテナンシー

構成オプション、テナント・ライフサイクル、そして分離の実際

IBM SDK Java Technology Edition バージョン 7 リリース 1 におけるマルチテナンシーの実装を詳しく探る

Comments

コンテンツシリーズ

このコンテンツは全#シリーズのパート#です: Java のマルチテナンシー

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:Java のマルチテナンシー

このシリーズの続きに乞うご期待。

IBM SDK Java Technology Edition バージョン 7 リリース 1 では、マルチテナント型 JVM をテクノロジー・プレビューとして利用することができます。この機能を利用すると、従来の JVM を共有する場合よりも、複数のアプリケーション・デプロイメントのパフォーマンスおよび分離レベルを高めることができます。以前の developerWorks 記事「Java マルチテナンシーの紹介」では、以下の概要を説明しています。

  • マルチテナント型 JVM を使用するメリットとそれに伴う代償
  • マルチテナント型 JVM の使用方法
  • 静的フィールドの分離を実現する方法
  • リソースの制約 (RCM (Resource Consumption Management) と呼ばれます) によるリソースの分離

前回の記事には、マルチテナンシー・フレームワークの単純さも浮き彫りにされています。Java アプリケーションをマルチテナント型 JVM におけるテナントとして実行する上で必要なことは、以下のように、-Xmt をコマンドラインに追加することのみです。

./java -Xmt Hello

この記事では、マルチテナンシー・フレームワークの 2 つの領域について詳しく探ります。まず、マルチテナント型 JVM では、アプリケーションがどのように実行されるのかを深く理解できるように、テナントのライフサイクルをひと通り説明します。ライフサイクルを説明する中で、フレームワークでの以下の構成オプションについても紹介します。

  • テナント・アプリケーションにオプションを渡す方法
  • テナント・アプリケーションを実行する JVM デーモン・プロセス (javad) にオプションを渡す方法
  • 特定の JVM デーモン・プロセスでテナント・アプリケーションをターゲットにする方法

記事で詳細を取り上げるもう 1 つの領域としては、実行中のアプリケーションで静的フィールドを分離するメリットを具体的に説明します。

サンプル・アプリケーションおよび構成

この記事ではサンプル・アプリケーション (皆さんがコンパイルして実行できるアプリケーション) を用いて、構成オプションを具体的に説明し、テナントのライフサイクルをひと通り説明します。さらに、このサンプル・アプリケーションを実行するためにコマンドラインで入力するコマンドと、JVM デーモン・プロセスを構成するための javad.options ファイルも紹介します (記事のサンプル・コード、コマンド、スクリプトのすべてを入手するには、「ダウンロード」を参照してください)。

リスト 1 に、サンプル・アプリケーションのコードを記載します。

リスト 1. サンプル・アプリケーション
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Hello {

   public static void main(String[] args) {

      InputStreamReader inputStream = new InputStreamReader(System.in);
      BufferedReader reader = new BufferedReader(inputStream);

      System.out.println("What is your name?");

      try {
         String name = reader.readLine();
         System.out.println("Hello " + name);
      } catch (IOException e) {
         e.printStackTrace();
      }
   }
}

このアプリケーションをテナントとして起動するためにコマンドラインで入力するコマンドは、以下のとおりです (1 行で入力してください)。

java -Xmt -Djavad.home=/tmp/multitenant_daemons -Xmx100M 
-Xlimit:netIO=5M-100M -DexampleProperty=1 -jar hello.jar

java -Xmt コマンドラインに指定するオプションは、テナントとして実行されるこのアプリケーションにだけ適用されます。上記の各オプションの意味は、以下のとおりです。

-Xmt
Java ランチャーに、アプリケーションをテナント・アプリケーションとして起動するように指示します。
-Djavad.home=/tmp/multitenant_daemons
特定の JVM デーモン・プロセスをターゲットとして指定します。-Djavad.home を指定しない場合は、このユーザーが実行するすべてのテナントが、同じ JVM デーモンの下で実行されます。
-Xmx100M
JVM デーモン・プロセスに対し、JVM のオブジェクト・ヒープのうち、このテナントが使用する領域を 100 MB に制限するように指示します。JVM のオブジェクト・ヒープの合計サイズは、javad.options ファイルに指定されます。テナントが 100 MB を超える領域を使用しようとすると、そのテナントは OutOfMemoryError (OOME) を受け取りますが、この OOME によって、同じデーモンの下で実行されている他のテナントが影響を受けることはありません。
-Xlimit:netIO=5M-10M
JVM デーモンに対し、このテナント用にネットワーク I/O の 5 メガバイト/秒 (MBps) を確保し、その一方でテナントを 10 MBps に制限するように指示します。JVM デーモンがこの最小帯域幅を確保できない場合には、テナント・ランチャーにエラー・メッセージが送信されます。この場合、テナント・アプリケーションは起動されません。
-DexampleProperty=1
この Java プロパティーをこのテナントからのみ見えるようにします。JVM デーモン・プロセスのこのインスタンスで実行される他のすべてのテナントからは、このプロパティーは見えません。

JVM デーモン自体へのオプションを渡す唯一の方法は、javad.options ファイルを使用することです。リスト 2 に、サンプル・アプリケーションを実行する JVM デーモンに使用する javad.options ファイルを記載します。

リスト 2. javad.options
# Option file for javad
-Xtenant
-Xrcm:consumer=autotenant
-Djavad.persistAfterEmptyTime=1
-Xmx2G
-Xdump:java:label=/tmp/multitenant_daemons/javacore.%pid.%seq.txt

上記の javad.options ファイルに指定されているオプションの内容は、以下のとおりです。

-Xtenant -Xrcm:consumer=autotenant
デーモン・プロセスとして機能する JVM のインスタンスに、マルチテナント・モードで実行するよう指示します。
-Djavad.persistAfterEmptyTime=1
JVM デーモン・プロセスに、最後のテナントがシャットダウンしてから 1 分後に終了するよう指示します。
-Xmx2G
JVM デーモンの最大オブジェクト・ヒープ・サイズを 2 GBに指定します。この JVM デーモンで実行されるすべてのテナントは、この 2 GB のオブジェクト・ヒープの一部を使用します。
-Xdump:java:label=/tmp/multitenant_daemons/javacore.%pid.%seq.txt
JVM デーモンが生成する javacore ファイルに、このオプションで指定されたファイル名を設定します (このオプションの詳細については、ユーザー・ガイドを参照してください)。

サンプル・アプリケーションで使用するオプションを理解したところで、次は、このアプリケーションを実行する環境をセットアップします。

環境のセットアップ

アプリケーションを実行するためのコマンドラインでは、-Djavad.home を指定しているため (つまり、特定の JVM デーモン・プロセスをターゲットにすることができます)、-Djavad.home ディレクトリーを作成する必要があります。

mkdir /tmp/multitenant_daemons

次に、カレント・ディレクトリーを /tmp/multitenant_daemons に変更して、SDK に含まれている javad.options ファイルをこのディレクトリーにコピーし、サンプル・アプリケーション用に以下のオプションを追加します。

cd /tmp/multitenant_daemons
cp /tmp/Java7R1_SR1/jre/bin/javad.options .
echo -Djavad.persistAfterEmptyTime=1 >> javad.options
echo -Xdump:java:label=/tmp/multitenant_daemons/javacore.%pid.%seq.txt >> javad.options
echo -Xmx2G >> javad.options

cat javad.options を実行して javad.options ファイルの内容を調べ、その内容が正しいことを確認します。

# Option file for javad
-Xtenant
-Xrcm:consumer=autotenant
-Djavad.persistAfterEmptyTime=1
-Xdump:java:label=/tmp/multitenant_daemons/javacore.%pid.%seq.txt
-Xmx2G

現時点では、新しく作成した -Djavad.home ディレクトリーには javad.options ファイルしか含まれていませんが、JVM デーモン・プロセスは起動後に、このディレクトリーに接続情報を書き込みます。

テナントのライフサイクルを探る

ここまでで環境を構成し、サンプル・アプリケーションとその構成を用意できたので、次はテナントのライフサイクルをひと通り詳しく見ていきます。テナントのライフサイクルを構成する主なステージは、以下のとおりです。

  1. テナント・ランチャーと JVM デーモンの起動
  2. アプリケーションの実行
  3. テナントのシャットダウン
  4. JVM デーモンのシャットダウン

ステージ 1: テナント・ランチャーと JVM デーモンの起動

Java アプリケーションが (-Xmt コマンドライン・オプションを使用して) テナントとして起動されると、テナント・ランチャーは、JVM デーモン・プロセスがすでに実行中であるかどうかを調べます。プロセスが実行中でない場合、テナント・ランチャーは JVM デーモン・プロセスを起動します。JVM デーモン・プロセスが起動されると、そのプロセスとテナント・ランチャーの間で、何かしらの「ハンドシェーク」が行われます。このハンドシェークの一環として、ランチャー・プロセスの環境とテナント・コマンドラインのオプションが JVM デーモンに送信されます。これにより、その JVM デーモン・プロセスの下で、テナント・アプリケーションが実行されることになります。

このシーケンスを、サンプル・アプリケーションのコンテキストで詳しく見てみましょう。まずは、以下のテナント・ランチャーのコマンドライン (1 行として入力) を実行することから始めます。

java -Xmt -Djavad.home=/tmp/multitenant_daemons -Xmx100M -Xlimit:netIO=5M-100M 
-DexampleProperty=1 -jar /tmp/hello.jar

Java ランチャーは、コマンドラインで -Xmt を見つけると、テナント・ランチャーになります。

テナント・ランチャーは -Djavad.home=/tmp/multitenant_daemons を読み取り、その結果として /tmp/multitenant_daemons を調べて、JVM デーモン・プロセスがこのユーザーとディレクトリーにすでに関連付けられているかどうかを判別します。ランチャーは、該当する JVM デーモン・プロセスが存在しないと判断すると、JVM デーモン・プロセスを起動します。前に説明したように、JVM デーモン・プロセスには javad.options ファイルに格納されているオプションが渡されます

起動後の JVM デーモン・プロセスは、自身の存在を広く知らせる (「アドバータイズ」する) ために、-Djavad.home で指定されたディレクトリーに接続情報を書き込みます。その際、この接続情報を保管する新しいディレクトリーが .java.uid という形式の名前で作成されます。例えば、以下に示されている新規ディレクトリーの名前は、.javad.4068 となっています。ここで、4068 は現行ユーザーの UID です。

dave /tmp/multitenant_daemons  $ ls -altr
total 32
-rwxr-xr-x 1 dave bgroup 164 Jun 26 17:04 javad.options
drwxrwxrwt. 10 root root 20480 Jun 26 17:05 ..
drwxr-xr-x 3 dave bgroup 4096 Jun 26 17:05 .
drwxr-xr-x 2 dave bgroup 4096 Jun 26 17:05 .javad.4068
dave /tmp/multitenant_daemons $

UID は、JVM デーモン・プロセスを現行ユーザーに関連付けるために使用されます。.java.4068 ディレクトリー内のファイルへのアクセスは、この JVM デーモンの作成を開始した現行ユーザーだけに制限されるため、他のユーザーは JVM デーモン・プロセスに接続することができません。

dave /tmp/multitenant_daemons $ ls -altr .javad.4068
total 12
drwxr-xr-x 3 dave bgroup 4096 Jun 26 17:05 ..
drwxr-xr-x 2 dave bgroup 4096 Jun 26 17:05 .
-rw------- 1 dave bgroup 103 Jun 26 17:05 4068
dave /tmp/multitenant_daemons $

テナント・ランチャーは .javad.4068 ディレクトリーに格納されている接続情報を読み取って、JVM デーモン・プロセスに接続するためのソケットを作成します。テナント・ランチャーと JVM デーモン・プロセスの間の以降の通信はすべて、このソケットを通じ、内部で指定された接続プロトコルを使用して行われます。

テナント・ランチャーは、自身のコマンドライン、そして自身の環境変数の完全なコピーを javad デーモン・プロセスに送信した後、javad プロセスからのメッセージを待機します。

JVM デーモンが -Xlimit:netIO=5M-100M を読み取ると、最小帯域幅の要件である 5 MBps を満たせることを確認します。JVM デーモンはこのチェックを行う方法として、すべてのテナントの最小帯域幅の要件の合計が、rcm.xml に指定されている値を超えていないことを確認します。

確認が取れると、JVM デーモンはテナントを作成して、テナント・ランチャーが指定する Java アプリケーションを実行します。

起動の最終部分で必要となるのは、テナント・ランチャーと JVM デーモン・プロセスを接続するソケット・チャネルに、テナント・アプリケーションの System.out、System.in、および System.err をリダイレクトすることです。

ステージ 2: アプリケーションの実行

この時点で、JVM はアプリケーションを実行可能な状態になったので、JVM デーモンは新しく作成されたテナントで main メソッドを呼び出します。リスト 1 に記載したコードの各セクションで何が行われるのかを見てみましょう。

最初の部分は、メッセージを書き込むだけに過ぎません。

public static void main(String[] args) {

   InputStreamReader inputStream = new InputStreamReader(System.in);
   BufferedReader reader = new BufferedReader(inputStream);

   System.out.println("What is your name?");

JVM デーモンは System.out へのこの書き込みをインターセプトし、ソケットを介してテナント・ランチャーにリダイレクトします。これにより、テナント・ランチャーはそのコンソールに「What is your name?」というメッセージを書き込みます。

dave /tmp/multitenant_daemons $ /tmp/Java7R1_SR1/jre/bin/java 
-Xmt -Djavad.home=/tmp/multitenant_daemons -Xmx100M -Xlimit:netIO=5M-100M 
-DexampleProperty=1 -jar /tmp/hello.jar
What is your name?

次の部分では、標準入力から読み取ります。

String name = reader.readLine();

JVM デーモン・プロセスは、System.in でこの要求をインターセプトし、ユーザー入力を待機中であることを伝えるメッセージをテナント・ランチャーに送信します。

デバッグのための情報収集

アプリケーションがユーザー入力を待機している間、ちょうど良い機会なのでデバッグに役立つように少し寄り道をして、どのようにすれば実行中のアプリケーションに関する情報を収集できるかを見ていきましょう。情報を収集するにはまず、テナント・ランチャーを、アプリケーションを実行している JVM デーモン・プロセス (javad) にマッピングするところから始めます。JVM デーモン・プロセスが実行している内容を確認する上で javacore ファイルを生成する必要がある場合に、このマッピングは役立ちます。

テナント・ランチャーに対応する JVM デーモンのプロセス ID を調べるには、ps -ef を実行します。デーモンとランチャーは、-Djavad.home で指定された同じディレクトリーを持っているため、互いをマッピングすることができます。次に、SIGQUIT を注入します。

dave /tmp/multitenant_daemons $ ps -ef | grep javad
dave 5092 3632 0 17:05 pts/1 00:00:00 /tmp/Java7R1_SR1/jre/bin/java -Xmt 
-Djavad.home=/tmp/multitenant_daemons -Xmx100M 
-Xlimit:netIO=5M-100M -DexampleProperty=1 -jar /tmp/hello.jar
dave 5094 5092 2 17:05 ? 00:00:01 /tmp/Java7R1_SR1/jre/bin/javad -Djavad.home=/tmp/multitenant_daemons
dave 5164 3543 0 17:07 pts/0 00:00:00 grep javad
dave /tmp/multitenant_daemons $ kill -QUIT 5094
dave /tmp/multitenant_daemons $

JVM デーモン・プロセスは SIGQUIT を受信すると、javad.options ファイル内で -Xdump によって指定されたディレクトリーに javacore ファイルを生成します。すると、テナント・ランチャーに JVMDUMP メッセージがブロードキャストされて返されます。

dave /tmp/multitenant_daemons $ /tmp/Java7R1_SR1/jre/bin/java -Xmt 
-Djavad.home=/tmp/multitenant_daemons -Xmx100M -Xlimit:netIO=5M-100M 
-DexampleProperty=1 -jar /tmp/hello.jar
What is your name?
JVMDUMP039I Processing dump event "user", detail "" at 2014/06/26 17:07:20 - please wait.
JVMDUMP032I JVM requested Java dump using 
'/tmp/multitenant_daemons/javacore.5094.0001.txt' in response to an event
JVMDUMP010I Java dump written to /tmp/multitenant_daemons/javacore.5094.0001.txt
JVMDUMP013I Processed dump event "user", detail "".

JVMDUMP メッセージはすべて、JVM デーモンに接続されたすべてのテナント・ランチャーにブロードキャストされます。したがって、JVM デーモン・プロセスが失敗した場合は、すべてのテナント・ランチャーに通知されます。

JVM デーモン (javad) プロセスを識別して、javad.options ファイルにダンプ・オプションを設定した後は、この手法を使用して、マルチテナント型 JVM の実行中にあらゆる標準 JVM 診断を入手することができます。

再びアプリケーションの実行についての説明

話を本題に戻して、main メソッドの実行について見ていきましょう。「Dave」と入力してください。テナント・ランチャーは、この入力を JVM デーモン・プロセスに送信します。

JVM デーモン・プロセスが「Dave」を受信すると、これをテナント・アプリケーションの System.in に送信します。

すると、テナント・アプリケーションが System.out に「Hello Dave」と書き込みます。

System.out.println("Hello " + name);

JVM デーモンは System.out へのこの書き込みをインターセプトし、ソケットを介してテナント・ランチャーにリダイレクトします。

テナント・ランチャーが「Hello Dave」を受信すると、これをコンソールに出力します。この時点で main メソッドが完了し、テナントはシャットダウン・フェーズに進みます。

ステージ 3: テナントのシャットダウン

マルチテナンシー・フレームワークで実行しているアプリケーションのシャットダウンと、非マルチテナント型 JVM で実行しているアプリケーションのシャットダウンとの主な違いは、マルチテナンシーの場合、アプリケーションを実行している JVM デーモン・プロセスは Java アプリケーションと一緒には終了しないことです。

一方、テナントとして実行されている Java アプリケーションの観点からすると、シャットダウンの動作に変わりはありません。

  1. アプリケーションは、デーモン以外のスレッドが終了するのを待機します。
  2. JVM デーモンによって、アプリケーションのシャットダウン・フックが実行されます。
  3. JVM デーモンによって、アプリケーションの残りのデーモン・スレッドがすべて終了されます。
  4. テナント・ランチャーが、Java アプリケーションによって指定された終了コードで終了します。

ステップ 4 は、JVM デーモンがアプリケーションの終了コードを含めたメッセージをテナント・ランチャーに送信することによって行われます。この例の場合、テナント・ランチャーは終了コード 0 で終了します。

dave /tmp/multitenant_daemons $ /tmp/Java7R1_SR1/jre/bin/java -Xmt -Djavad.home=
/tmp/multitenant_daemons -Xmx100M -Xlimit:netIO=5M-100M -DexampleProperty=1 -jar /tmp/hello.jar
What is your name?
JVMDUMP039I Processing dump event "user", detail "" at 2014/06/26 17:07:20 - please wait.
JVMDUMP032I JVM requested Java dump using '/tmp/multitenant_daemons/javacore.5094.0001.txt' 
in response to an event
JVMDUMP010I Java dump written to /tmp/multitenant_daemons/javacore.5094.0001.txt
JVMDUMP013I Processed dump event "user", detail "".
Dave
Hello Dave
dave /tmp/multitenant_daemons $ echo $?
0
dave /tmp/multitenant_daemons $

ステージ 4: JVM デーモンのシャットダウン

デフォルトでは、JVM デーモンは無期限に動作し続けます。しかしながら、この例では javad.options ファイルで -Djavavd.persistAfterTime=1 を指定しているため、テナント・アプリケーションがシャットダウンしてから 1 分後に JVM デーモン・プロセスが終了することになります。このタイムアウトが発生する前に別のテナント・ランチャーからの接続があった場合は、JVM デーモン・プロセスに接続されているテナントがなくなった時点で、1 分のタイマーが再スタートします。

これで、テナント・ライフサイクルの詳細の説明は完了です。次のセクションでは、同じ JVM デーモンで実行されているテナント間で実施される分離について詳しく探ります。

分離の実際

アプリケーションをそのままの形で実行できること、あるいはほんのわずかな変更を加えるだけで実行できることが、分離によって実現される主なメリットです。

マルチテナント型 JVM は、テナント間に一定レベルの分離を設けることで、あるアプリケーションが他のアプリケーションの処理に直接影響を及ぼし得る範囲を制限します。このセクションでは、この主要な機能のメリットについて、皆さんが自分でコンパイルして実行できるサンプル・コードを通して探ります。サンプル・コードを実行するには、この機能をサポートする Java コードと Linux スクリプトをダウンロードしてください。

Java マルチテナンシーの紹介」で説明したように、分離を実現する主要な要素は、静的フィールドの分離です。この概念を実際のアプリケーションの観点から理解できるように、最初にリスト 3 に示す 2 つの単純なアプリケーションを取り上げます。

リスト 3. 2 つの単純なアプリケーション
public class StillHere extends ExampleBase {
   public static void main(String[] args) {
      while(notDone()) {
         println(args, "Still Here");
         sleep(SECONDS_2);
      }
      println(args, "Done");
   }
}

public class Goodbye extends ExampleBase {
   public static void main(String[] args) {
      println(args, "Hello");
      sleep(SECONDS_4);
      println(args, "About to exit, Goodbye");
      System.exit(-1);
   }
}

1 番目のアプリケーションは、終了するまで 2 秒間隔で「Still Here」というメッセージを出力します。2 番目のアプリケーションは、「Hello」というメッセージを出力した後、4 秒待機してから System.exit() を呼び出します。これらの (明らかにちっぽけな) アプリケーションは、以下のようなアプリケーションを表します。

  • 周期タスクを実行しながら、稼働し続けるアプリケーション
  • 何らかの処理を実行した後に終了するアプリケーション

この 2 つのアプリケーションは、以下のコマンドラインのコマンドによって起動されると、同じ javad プロセス内で並行して実行されます。

./java -Xmt -cp isolationExamples.jar StillHere app1 & 
./java -Xmt -cp isolationExamples.jar Goodbye app2

この 2 つのアプリケーションは、通常の JVM で実行することもできます。それにはまず、アプリケーションをラッパー・クラスの中に収容します (リスト 4 を参照)。

リスト 4. 2 つのアプリケーションを従来の JVM で実行する場合のラッパー・クラス
public class HelloGoodBye extends StandardJVMRunner{
   public static void main(final String[] args) {
      Thread stillHereThread = new Thread() {
         public void run() { StillHere.main(APP1); }
      };
      
      Thread goodByeThread = new Thread() {
         public void run() { Goodbye.main(APP2); }
      };
      stillHereThread.start();
      goodByeThread.start();
   }
}

これで、以下のコマンドラインを使用してアプリケーションを実行することができます。

./java -cp isolationExamples.jar HelloGoodBye

アプリケーションを実行する 2 通りの方法の間にある大きな違いの 1 つは、分離の程度です。マルチテナンシー機能を使わずにアプリケーションを同時に実行すると、以下のような出力になります。

Run in normal JVM
app1 [Still Here]
app2 [Hello]
app1 [Still Here]
app2 [About to exit, Goodbye]
app1 [Still Here]
app1 [Still Here]

app2 が System.exit() を呼び出してから間もなくすると、app1 からの出力が示されなくなっていることに注目してください。その原因は、app2 での System.exit() の呼び出しによって、共有 JVM プロセスが終了し、それとともに app1 が終了したためです。これは、あるアプリケーションが別のアプリケーションに直接影響を与える極端な例ですが、このようにアプリケーションが分離されていないと、Goodbye アプリケーションが Still Here アプリケーションを終了させることになります。

今度は、同じ 2 つのアプリケーションをマルチテナント型 JVM で実行します。

Run in MT JVM
app1 [Still Here]
app2 [Hello]
app1 [Still Here]
app1 [Still Here]
app2 [About to exit, Goodbye]
dave /tmp/dave/apr16/jre/bin $ app1 [Still Here]
app1 [Still Here]
app1 [Still Here]
app1 [Still Here]
app1 [Still Here]
app1 [Done]

ご覧のように、app2 が System.exit() を呼び出した後も app1 は実行され続け、app1 は最終的に正常に終了して「Done」を出力します。マルチテナント型 JVM が可能にする分離によって、Goodbye アプリケーションは同じプロセスで実行されているもう一方のアプリケーションに影響を与えることなく、既存のコードを実行して終了できるようになります。

マルチテナント型 JVM は単一のプロセスを共有することから、アプリケーションは、通常 JVM プロセスに影響を及ぼすような呼び出しを実行できると同時に、アプリケーション自体への影響を制限することができます。System.exit() は単純な例ですが、これと同じ原則は、シャットダウン・フック、システム入力/出力をはじめ、他の数多くの場合にも当てはまります。この例からわかるように、分離の機能は、アプリケーション自体に変更を加えなくても、あるアプリケーションが他のアプリケーションに影響を与える可能性を制限します。アプリケーションをそのままの形で実行できること、あるいはほんのわずかな変更を加えるだけで実行できることは、分離によって実現される主なメリットの 1 つです。

次に、多少複雑な例として、2 つのアプリケーションでそれぞれに異なる (人間の) 言語をサポートする場合を考えてみます。リスト 5 に、この両方のアプリケーションを記載します。

リスト 5. 異なる言語を使用する 2 つのアプリケーション
 public class OutputInDefaultLocale extends ExampleBase {
   public static void main(String[] args) {
      while(notDone()) {
         Locale localeToUse = Locale.getDefault(); 
         ResourceBundle messages = ResourceBundle.getBundle("Messages",localeToUse);
         println(args, messages.getString( "HELLO_KEY"));
         sleep(SECONDS_2);
      }
      println(args, "Done");
   }
}
public class ChangeLocale extends ExampleBase {
   public static void main(String[] args) {
      try { Thread.sleep(SECONDS_4); } catch (Exception e) {};
      println(args, "Changing default locale from:" + Locale.getDefault());
      Locale.setDefault(new Locale("fr","CA"));
      println(args, "Locale is now:" + Locale.getDefault());
      OutputInDefaultLocale.main(args);
   }
}

バンドル・ファイルは以下のとおりです。

Messages.properties  -  content ? HELLO_KEY=Hello
Messages_fr.properties? content ? HELLO_KEY=Bonjour

1 番目のアプリケーション (OutputInDefaultLocale) は、デフォルトのロケールを取得し、終了するまで 2 秒ごとにデフォルトの言語で「Hello」というメッセージを出力します。2 番目のアプリケーション (ChangeLocale) は、4 秒待機した後、デフォルトのロケールをフランス語に変更します。これにより、以降、「Hello」と書き込むと、「Bonjour」と出力されます。

前の例と同じく、以下のコマンドラインのコマンドでマルチテナンシー機能を利用することにより、この 2 つのアプリケーションをテナントとして同時に実行することができます。

./java -Xmt -cp isolationExamples.jar OutputInDefaultLocale app1 & 
./java -Xmt -cp isolationExamples.jar ChangeLocale app2

また、前の例と同じくラッパーを使用して、アプリケーションを標準の JVM で実行することもできます。リスト 6 に、ラッパーを記載します。

リスト 6. OutputInDefaultLocale および ChangeLocale アプリケーションを実行するためのラッパー
public class LocaleIssues extends StandardJVMRunner {
   public static void main(final String[] args) {
      Thread outputInDefaultLocalThread = new Thread() {
         public void run() { OutputInDefaultLocale.main(APP1); }
      };
      
      Thread changeLocaleThread = new Thread() {
         public void run() { ChangeLocale.main(APP2); }
      };
      
      outputInDefaultLocalThread.start();
      changeLocaleThread.start();
   }
}

マルチテナンシー機能を使わずにアプリケーションを実行する場合のコマンドラインへの入力は、以下のとおりです。

./java -cp isolationExamples.jar LocaleIssues

マルチテナンシー機能を使用しない場合、出力は以下のようになります。

Run in normal JVM
app1 [Hello]
app1 [Hello]
app2 [Changing default locale from:en_US]
app2 [Locale is now:fr_CA]
app2 [Bonjour]
app1 [Bonjour]
app2 [Bonjour]
app1 [Bonjour]
app2 [Bonjour]
app1 [Bonjour]
app2 [Done]
app1 [Done]

デフォルトのロケールは en_US なので、1 番目のアプリケーションが実行を開始すると、メッセージは英語で出力されます。2 番目のアプリケーションは 4 秒間待機した後、ロケールがフランス語の fr_CA に変更されます。その時点から、両方のアプリケーションの出力はフランス語になります。待ってください ― これは私たちが望んでいる結果ではありません!しかし、両方のアプリケーションが JVM を共有していて、デフォルトのロケールは 1 つしかないことから、このような結果になるのは当然です。

今度はマルチテナンシー機能を使用して、同じ 2 つのアプリケーションを実行します。

Run in MT JVM
app1 [Hello]
app1 [Hello]
app2 [Changing default locale from:en_US]
app2 [Locale is now:fr_CA]
app2 [Bonjour]
app1 [Hello]
app2 [Bonjour]
app1 [Hello]
app2 [Bonjour]
app1 [Hello]
app1 [Done]
app2 [Bonjour]
app2 [Done]

2 番目のアプリケーションがデフォルトのロケールを変更した後、1 番目のアプリケーションは英語で出力し続け、2 番目のアプリケーションはフランス語で出力します。これが、私たちが望んでいる結果です。マルチテナント型 JVM による分離は、アプリケーションを変更しなくても、この 2 つのアプリケーションがそれぞれに正しい動作を維持しながら、同じプロセスを共有することを可能にします。この例の場合、デフォルトのロケールは静的フィールドに格納されていますが、マルチテナンシー機能によって実現される静的フィールドの分離により、それぞれのアプリケーション (テナント) は固有のロケールを使用できるようになります。

まとめ

この記事では、「Java マルチテナンシーの紹介」の続編として、マルチテナント型 JVM とその動作を詳細に見てきました。読者の皆さんは、テナント・アプリケーションのライフサイクルについての知識を深め、静的フィールドの分離がもたらすメリットをよく理解できたことと思います。

ぜひ、マルチテナント型 JVM をダウンロードしてデモを試し、ドキュメントを入手して、独自のアプリケーションを作成してください。また、IBM Multitenant JVM コミュニティーに参加して、最新情報を入手したり、このテクノロジーの方向付けに役立つフィードバックを提供したりすることもできます。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Java technology, Cloud computing
ArticleID=984074
ArticleTitle=Java のマルチテナンシー: 構成オプション、テナント・ライフサイクル、そして分離の実際
publish-date=10022014