CodeZine(コードジン)

特集ページ一覧

簡単に拡張できる軽量なモニタリングフレームワークの構築

JMXを使うまでもない規模における軽量なモニタリングフレームワークの実装

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2005/09/12 12:00

モニタリングサーバは、たとえばeコマースであれ通信ソリューションであれ、どんなサーバ側インフラストラクチャでもきわめて重要な役割を担っています。ここでは、拡張可能なオブジェクト指向フレームワークを利用して、自前のモニタリングシステムを構築する方法を紹介します。

はじめに

 開発者はたびたび、一連のアプリケーションのモニタリングを行い、障害発生時には復旧策を実行しなければならない、という状況に直面します。私の場合は、さまざまなOS上に分散した複数のコンポーネントから成るマルチレイヤの電子通信バックエンドシステム用のモニタリングサーバを設計する必要性に迫られました。要求分析を行った結果、わざわざJMXを使って解決するほどの事例ではないことがわかりました。そこで、拡張やカスタマイズが容易にできる軽量なJavaフレームワークを実装し、そのうえでモニタリングシステムを構築することにしたのです。

 本稿では、このフレームワークを使用して、データベース、アプリケーション、Webサーバのようなさまざまなバックエンドプロセスのモニタリングを行う方法と、プロセスの再起動、ディスク使用領域オーバー時のクリーンアップ、障害情報のレポートおよびログの生成のような修復処理を呼び出す方法を紹介します。本稿のダウンロードサンプルには、フレームワークのソースコードとサンプルクライアントが含まれています。

モニタリングコンポーネントの基本

 モニタリングの対象となるあらゆるコンポーネントに共通する属性を次に示します。

  1. コンポーネントの状態
  2. 「正常」「異常」「モニタリング不可」の3つのいずれかの値をとります。
  3. 修復処理
  4. プロセスがダウンした場合、この処理を実行することで回復が行われます。ただし、すべてのコンポーネントが障害から回復できるわけではありません。たとえば、回復不可能かつクリティカルなプロセスがダウンした場合にシステムにできるのは、アラームを発することくらいです。
  5. モニタリングの頻度
  6. どの程度の頻度でコンポーネントのモニタリングを行うかを表します。頻度カウンタは設定可能なパラメータです。コンポーネントの種類に応じて、秒単位または分単位で値を指定します。
  7. クリティカル性
  8. モニタリングされるコンポーネントのクリティカル性の高さを定めます。「高」「中」「低」の3つのレベルのいずれかに設定します。なお、モニタリングの頻度は低くてもクリティカル性の高いコンポーネントが存在しうることに注意してください。

フレームワークのもとで

 図1のクラス図に示すように、このフレームワークは4つのクラスと1つのインターフェイスで構成されます。このフレームワークを呼び出すクライアントコードには、InfoインターフェイスとLWMFrameworkクラスだけが公開されます。こうすることで、クライアント側はフレームワーク内部の実装を意識せずに済むわけです。

図1 フレームワークのクラス図
図1 フレームワークのクラス図

 モニタリングが必要なコンポーネントには、すべてこの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つのバックグラウンドスレッドが使われています。

  1. 各コンポーネントのステータスを確認するStatusMonitor
  2. 修復処理の実行とステータスの報告を行うStatusManager

 StatusMonitorクラス(リスト1参照)は、毎秒あるいは設定した時間間隔ごとにポーリングを行い、モニタリング可能なオブジェクトのカウンタ値が期限切れになっていないかを確認します。期限切れの場合は、StatusMonitorクラスはそのオブジェクトに対してmonitor()メソッドを実行します。

リスト1 StatusMonitorクラス
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オブジェクトに関連付けられています。また、カウンタ(モニタリング頻度)やクリティカル性といった属性は、モニタリング可能オブジェクトそのもののメンバとして用意されています。

リスト2 Monitorableクラス
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スレッドに対して通知を行います。StatusMonitorStatusManagerとの通信は、このモニタリング可能オブジェクトを介して行われます。

 モニタリング可能オブジェクトによってsetChangedメソッドの呼び出しが行われると、StatusManager(リスト3参照)が通知を受け取ります。通知を受けたStatusManagerクラスは、該当するモニタリング可能オブジェクトをキューから取り出します。取り出したモニタリング可能オブジェクトの状態が「異常」であれば、StatusManagerクラスはそのオブジェクトのexecute()メソッドを実行します。これにより、モニタリング可能オブジェクトに関連付けられたInfoオブジェクトのprocess()メソッドが呼び出されて、修復処理が実行に移されます。

リスト3 StatusManagerクラス
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の各スレッドが開始されるのがわかります。

図2 フレームワークのシーケンス図
図2 フレームワークのシーケンス図

同期化

 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参照)の中で設定できます。

リスト4 XMLによる設定(「Monitor.xml」)
<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のようなハイエンドの管理ソフトウェアに簡単に統合することができます。



  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加

あなたにオススメ

著者プロフィール

バックナンバー

連載:japan.internet.com翻訳記事

もっと読む

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5