はじめに
開発者はたびたび、一連のアプリケーションのモニタリングを行い、障害発生時には復旧策を実行しなければならない、という状況に直面します。私の場合は、さまざまなOS上に分散した複数のコンポーネントから成るマルチレイヤの電子通信バックエンドシステム用のモニタリングサーバを設計する必要性に迫られました。要求分析を行った結果、わざわざJMXを使って解決するほどの事例ではないことがわかりました。そこで、拡張やカスタマイズが容易にできる軽量なJavaフレームワークを実装し、そのうえでモニタリングシステムを構築することにしたのです。
本稿では、このフレームワークを使用して、データベース、アプリケーション、Webサーバのようなさまざまなバックエンドプロセスのモニタリングを行う方法と、プロセスの再起動、ディスク使用領域オーバー時のクリーンアップ、障害情報のレポートおよびログの生成のような修復処理を呼び出す方法を紹介します。本稿のダウンロードサンプルには、フレームワークのソースコードとサンプルクライアントが含まれています。
モニタリングコンポーネントの基本
モニタリングの対象となるあらゆるコンポーネントに共通する属性を次に示します。
- コンポーネントの状態
- 修復処理
- モニタリングの頻度
- クリティカル性
フレームワークのもとで
図1のクラス図に示すように、このフレームワークは4つのクラスと1つのインターフェイスで構成されます。このフレームワークを呼び出すクライアントコードには、Info
インターフェイスとLWMFramework
クラスだけが公開されます。こうすることで、クライアント側はフレームワーク内部の実装を意識せずに済むわけです。
モニタリングが必要なコンポーネントには、すべてこのInfo
インターフェイスを実装しなければなりません。isOk()
メソッドによってコンポーネントのステータスをチェックし、process()
メソッドによって修復処理のハンドリングを行います。
public interface Info { //checks the status //returns true if ok //throws Exception if it could not be monitored public boolean isOk() throws Exception; //handles corrective action public boolean process(); }
このフレームワークでは、次の2つのバックグラウンドスレッドが使われています。
- 各コンポーネントのステータスを確認する
StatusMonitor
- 修復処理の実行とステータスの報告を行う
StatusManager
StatusMonitor
クラス(リスト1参照)は、毎秒あるいは設定した時間間隔ごとにポーリングを行い、モニタリング可能なオブジェクトのカウンタ値が期限切れになっていないかを確認します。期限切れの場合は、StatusMonitor
クラスはそのオブジェクトに対してmonitor()
メソッドを実行します。
public class StatusMonitor extends Thread { private final int monInterval; //in milliseconds private final Monitorable[] monArray; public void run() { while(true) { for(int cnt = 0; cnt < Integer.MAX_VALUE; cnt++) { //iterate through the array for(int i = 0; i < monArray.length; i++) { //check if counter expired if((cnt % monArray[i].getCounter()) == 0) monArray[i].monitor(); } } try { sleep(monInterval); } catch(InterruptedException ie) {} } } }
モニタリング可能オブジェクトはMonitorable
クラス(リスト2参照)で表されます。個々のモニタリング可能オブジェクトは、StatusManager
オブジェクトおよびInfo
オブジェクトに関連付けられています。また、カウンタ(モニタリング頻度)やクリティカル性といった属性は、モニタリング可能オブジェクトそのもののメンバとして用意されています。
public class Monitorable { private int counter; private byte criticality; private byte prevStatus; private StatusManager manager; private Info info; public void monitor() { byte currentStatus = NOT_OK; try { if(info.isOk()) { currentStatus = Monitorable.OK; } } catch(Exception ex) { currentStatus = Monitorable.COULD_NOT_BE_MONITORED; } //call setchanged if there is change in status //or if status is not ok or cud not be monitored if((currentStatus != prevStatus) || currentStatus != StatusManager.OK) { synchronized(this) { prevStatus = currentStatus; } manager.setChanged(this); } } //return the last monitored status public synchronized byte getStatus() { return prevStatus; } //recovery action public void execute() { info.process(); } }
モニタリング可能オブジェクトは、Info
オブジェクトにおけるステータスの変化を絶えず把握し、StatusManager
スレッドに対して通知を行います。StatusMonitor
とStatusManager
との通信は、このモニタリング可能オブジェクトを介して行われます。
モニタリング可能オブジェクトによってsetChanged
メソッドの呼び出しが行われると、StatusManager
(リスト3参照)が通知を受け取ります。通知を受けたStatusManager
クラスは、該当するモニタリング可能オブジェクトをキューから取り出します。取り出したモニタリング可能オブジェクトの状態が「異常」であれば、StatusManager
クラスはそのオブジェクトのexecute()
メソッドを実行します。これにより、モニタリング可能オブジェクトに関連付けられたInfo
オブジェクトのprocess()
メソッドが呼び出されて、修復処理が実行に移されます。
public class StatusManager extends Thread { //add to the queue and //notify that the status of monitorable has changed public synchronized void setChanged (Monitorable monitorable) monQueue.add(monitorable); notifyAll(); } //get the Monitorable object from the queue public synchronized Monitorable getMonitorable() { // return the object from the queue while(monQueue.isEmpty()) { try { wait(); } catch(Exception ex) {} } return (Monitorable) monQueue.remove(0); } //report or execute corrective action private void processChange() { Monitorable monitorable = getMonitorable(); //take corrective action if(monitorable.getStatus() == NOT_OK) { monitorable.execute(); } //log or report the status } public void run() { while(true) processChange(); } }
今回の例では、StatusManager
スレッドのコンテキスト内で復旧タスクの実行を行っていますが、StatusManager
を拡張してスレッドプールを持たせれば、別のスレッドで復旧タスクを実行させることもできます。
このフレームワークの起動クラスであるLWMFramework
は、フレームワークの設定と初期化のために利用されます。図2のシーケンス図は、さまざまなフレームワークオブジェクト間のインタラクションを示しています。クライアントコードは、すべてのInfo
オブジェクトの追加を終えてからこのフレームワークを起動させ、その途中でStatusManager
およびStatusMonitor
の各スレッドが開始されるのがわかります。
同期化
StatusManager
クラスには、モニタリング可能オブジェクト用のスレッドセーフなキューが用意されています。スレッドは、このキューからモニタリング可能オブジェクトを取り出し、そのgetStatus()
メソッドを呼び出します。モニタリング可能オブジェクトのステータス変数へのアクセスもまた同期化されています。
フレームワークの利用
ここでは、Apache Webサーバのプロセスを監視し、ダウン時にはプロセスを再起動するという例を考えてみましょう。そのために、Info
インターフェイスを実装するProcessInfo
クラスを作成します。
リストされたプロセスの1つ1つに対して「apache.exe」プロセスが実行中であるかどうかをチェックするためのisOk()
メソッドをコーディングし、さらに「apache.exe」を起動するためのprocess()
メソッドをコーディングします。
public class ProcessInfo implements Info { ProcessInfo(String processName, String execName){ ..} //check if process is running or not boolean isOk() throws Exception {..} //re-launch process boolean process() {..}; }
現状のJavaでは、プロセス一覧、ディスク使用量、CPU負荷、ネットワーク接続状況などを取得するための手段はAPIレベルではサポートされていません。私のプロジェクトでは、こうした情報取得のための関数をOS別にC言語で書き、JavaのコードからアクセスできるようにJNIラッパを用意しました。かなり効率的ではありますが、JNIによる呼び出しがJVMプロセスのコンテキスト内で実行されるため、この解決法は少々危険だといえます。
代わりに、ネイティブ関数にアクセスするための別のプロセスを用意することもできるでしょう。本稿のダウンロードサンプルには、「CheckProcess.exe」というWin32アプリケーションが含まれています。このアプリケーションは、プロセス名をコマンドラインパラメータとして取得し、そのプロセスが実行中か否かに応じてTRUEまたはFALSEを標準出力に書き出します。ProcessInfo
クラスのisOk()
メソッドは、この「CheckProcess.exe」を実行し、そのプロセスの入力ストリームから実行中か否かのステータスを読み取ります。
public boolean isOk() throws Exception { String[] cmdArray = new String[] {“CheckProcess.exe”, “apache.exe”}; Process proc = Runtime.getRuntime().exec(cmdArray); BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream())); String status = br.readLine(); if(status.equals("TRUE")) return true; return false; }
続いて、ProcessInfo
オブジェクトを設定してフレームワークを開始させるためのTestClient
を次のように実装します。
public class TestClient { public static void main(String[] args) throws Exception { ProcessInfo p = new ProcessInfo(“apache.exe”, “<Path>/apache.exe”); int counter = 10; //10 seconds byte criticality = LWMFramework.HIGH; LWMFramework lwmf = new LWMFramework(); //set minimum monitorable frequency 1 second lwmf.setMonitorInterval(1); //add info object lwmf.add(p, counter, level); //start the framework lwmf.start(); Thread.currentThread().join(); } }
フレームワークの拡張
このフレームワークはいろいろな方法で拡張することができます。1つの方法として、リモートプロセス、CPU負荷、帯域利用率といったさまざまなコンポーネントを監視するための新しいInfo
オブジェクトを実装するやり方があります。こうした新しいInfo
オブジェクトは、XML形式の設定ファイル「Monitor.xml」(リスト4参照)の中で設定できます。
<MonitorServer interval="1" > <!-- The interval determines unit value of counter in seconds, if this value is 60 then minimum frequency of monitoring is 60 sec or 1 min !--> <MONITORABLE name = "Apache Webserver" class = "ProcessInfo" counter = "1000" criticality = "HIGH"> <!-- ProcessInfo specific configuration !--> <process name="apache.exe" exec="C:/Apache.exe"/> </MONITORABLE> <MONITORABLE name = "Tomcat" class = "ProcessInfo" counter = "1000" criticality = "HIGH"> <process name="tomcat.exe" exec="path../tomcat.exe"/> </MONITORABLE> <MONITORABLE name = "C Drive" class = "FileSystemInfo" counter = "100000" criticality = "HIGH"> <filesystem path="C:" threshold="80" exec="C:/../diskcleanup.exe"/> </MONITORABLE> <MONITORABLE name = "app server" class = "ConnectivityInfo" counter = "1000" criticality = "HIGH"> <connectivity host="198.168.1.10"/> </MONITORABLE> <MONITORABLE name = "LocalMachine" class = "CPUInfo" counter = "10000" criticality = "LOW"> <Threshold value="80"/> </MONITORABLE> </MonitorServer>
ここでは、モニタリング可能オブジェクトのそれぞれが、1つのXMLノードとして表され、名前、カウンタ、クリティカル性およびクラスといった共通の属性を持っています。このクラス属性は、Info
オブジェクトのインスタンスを作るときに利用されます。モニタリング可能オブジェクトを表すノード下の要素や子ノードは、対応するInfo
オブジェクトの設定を行うために利用されます。また、新たなInfo
オブジェクトを実装して「Monitor.xml」ファイルに追加することができます。そうすれば、フレームワークのソースコードを修正することなく新しいコンポーネントを追加することが可能になります。
このフレームワークをさらに拡張し、レポートや統計情報を生成するサードパーティアプリケーションとのインターフェイスを実装することもできます。また、StatusManager
クラスにHTTPインターフェイスを用意して、コンポーネントをWebブラウザから管理できるようにすることも可能です。
JMXの軽量な代替手段
ここで紹介したモニタリングフレームワークは簡単に拡張でき、とりわけサーバ側アプリケーションのモニタリングで大いに役立ちます。この軽量なフレームワークは、既存の管理ツールを完全に置き換えるものではありません。むしろ、そうした管理ツールを補完するものであり、HP OpenViewのようなハイエンドの管理ソフトウェアに簡単に統合することができます。