Mule ESB の jvm のメモリやスレッドを、Zabbix で監視する

Mule ESB の JVM のメモリやスレッドの状態を Zabbix で監視する。

方法

Zabbix の JMX ブリッジ、 zapcat を使う。
Zapcat JMX Zabbix Bridge How To

zabcatのダウンロード URL
ソース、jar、サンプル一式を入手できる。

Mule ESB への組み込み

Mule ESB で、zapcat を使うには、Spring Bean として、組み込めばOK.
ポート番号がデフォルト(10052) 以外に指定できるように、簡単な、ラッパークラスを作った。

package com.systemsekkei.monitoring.mule.jmx ;

import org.kjkoster.zapcat.Agent;
import org.kjkoster.zapcat.zabbix.ZabbixAgent;

import static org.kjkoster.zapcat.zabbix.ZabbixAgent.DEFAULT_PORT ;
import static org.kjkoster.zapcat.zabbix.ZabbixAgent.PORT_PROPERTY ;

public class ZabbixAgentBean
{
    private Integer port = DEFAULT_PORT;

    public void startZabbixAgent()
    { 
        System.getProperties().put( PORT_PROPERTY, port.toString() ) ;
        Agent agent = new ZabbixAgent();
    }

    public void setPort( Integer port )
    {
        this.port = port ;
    }
}

Mule の config ファイルの設定


	
		
			10053
			
	

jar の配置

Mule のユーザライブラリの配置場所 ( MULE_HOME/lib/user ) に、zapcat-1.2.jar と、上記の自作クラスをコンパイルして、jar にしたものを配置する。

起動

これで Mule を起動すれば、Zabbix での Mule ESB の JVM のメモリやスレッドの様子を監視できるようになる。

起動時のログメッセージに注意。

設定がおかしかったり、うまく起動しない場合は、起動時のログに何か、出力されている。

Zabbix サーバーを使わずに、簡単にテスト

telnet HOST 指定したポート番号

で、接続できればOK。

telnet 接続して、

jmx[java.lang:type=Runtime][VmVersion]

とか、zabbix のキー文字列を送れば、

ZBXD19.0-b09
ホストとの接続が切断されました。

という感じの応答がかえる。

親子関係のテーブル構造ごと、別のデータベースに転送する X-R マッピング

データベース間のデータ転送をするとき、親子関係のテーブル構造を、そのまま転送する方法。

メッセージングで、データベース間を連携するとき、親子関係のテーブル(たとえば、受注ヘッダと受注明細)を、そのままの構造で転送することがなかなかできずに困っていた。

親レコードと子レコードをばらばらに送ると、非同期なので、受け取った側のデータベースに書き込む時、参照整合性とかやっかいな問題がおきる。

結合して、一つのフラットファイルのイメージで、メッセージとして送る方法もある。これは、受け取った側で、親子関係の分解が面倒。

イデアとしては、親子関係の構造を、そのままXML ドキュメントにマッピングして、転送すれば良いとは思っていたが、記述が複雑になりそうで、避けていた。

今回、どうしても、そういう機能がほしくなり、Mule ESB + groovy スクリプトで、書いてみたら、意外と簡単に実現できた。

Mule ESB のサービス構成

<model name="db-xml-db Sample">

<service name="db to xml">
	<inbound>
		<jdbc:inbound-endpoint pollingFrequency="${jdbc.pollingFrequency}"
			queryKey="requestPolling"/>
	</inbound>

	<script:component>
		<script:script
			name="select and build xml"
			engine="groovy"
			file="script/db-xml.groovy"
		/>
	</script:component>

	<outbound>
		<pass-through-router>
			<vm:outbound-endpoint address="vm://save"/>
		</pass-through-router>
	</outbound>
</service>

<service name="xml to databese">

	<inbound>
		<vm:inbound-endpoint address="vm://save"/>
	</inbound>
	
	<script:component>
		<script:script
			name="parse xml and insert"
			engine="groovy"
			file="script/xml-db.groovy"
		/>
	</script:component>

</service>

</model>

db to xml の groovy スクリプト(抜粋)

//payload から 転送すべき id を取得

id = payload.id

def writer = new StringWriter() 
def xmlDocument = new groovy.xml.MarkupBuilder( writer )

// 親・子・孫のSELECTをしながら XML 生成
//
// xmlDocument.要素名( SELECT結果 ) の形式で、
// その要素名で、xml 要素を生成
// SELECT結果を、その要素の属性に、カラム名="値", ...  形式で記述
//

// 親レコード(XMLルート要素)を一件だけ取得する
// 複数行文字列は、トリプルクォートで記述

def oyaRecord = sql.firstRow( "親レコードSELECT文" )

xmlDocument.oya( oyaRecord ){ // oya要素 生成

  def listOfKo = sql.rows( "子レコードSELECT文" ) ;

	listOfKo.each{ koRecord -> 
		xmlDocument.ko( koRecord ) { // ko要素 生成

			def listOfMago = sql.rows( "孫レコード取得SELECT文" ) ;
			listOfMago.each{ magoRecord -> 
					xmlDocument.mago( magoRecord ) } // mago要素生成
		}
	}
}

xmlMessage = writer.toString() // xml をStringオブジェクトに変換
log.info( "XML送信:$xmlMessage" ) 

return xmlMessage // outbound に xmlMessage を渡す

生成した XML

<oya id='1' name='oya1'>
  <ko id='1' oya_id='1' name='ko1'>
    <mago id='1' ko_id='1' name='mago1' />
    <mago id='2' ko_id='1' name='mago2' />
  </ko>
  <ko id='2' oya_id='1' name='ko2'>
    <mago id='3' ko_id='2' name='mago3' />
    <mago id='4' ko_id='2' name='mago4' />
  </ko>
</oya>

xml to db の groovy スクリプト ( 抜粋 )

// payload から xml 要素を取り出しながら、データベースに出力
// groovy XmlParse 
// xml 要素名を、そのままオブジェクト名として参照できる

def oya = new XmlParser().parseText(payload);

insertOya( oya.attributes() )
oya.each { ko ->
    insertKo( ko.attributes() )
    ko.each { mago ->
       insertMago( mago.attributes() )
   }
}

def insertOya(map){ /* insert 文の実行 */ }
def insertKo(map){ /* insert 文の実行 */ }
def insertMago(map){ /* insert 文の実行 */ }

goovy の xml.MarkupBuilder、 util.XMLParser クラスを使うと、こんな感じで、書けちゃう。

親子関係だけではなく、2階層目に異なる要素(つまり、兄弟関係の要素)の構造でも、同じ感じで、書ける。

X-R マッピングが、こんなに簡単にかければ、いろいろ使えそうです。

参考URL:
実用的な Groovy: XML を作成し、構文解析し、容易に扱う
http://www.liquibase.org/ja/generate-changelog-with-groovy

canoo webtest で、オートパイロットと HTMLページの解析処理

Canoo Webtest で、

  1. 自動ログイン
  2. 検索実行
  3. 検索結果の加工

という処理を、書いてみた。

groovy で、htmlunit API を使えるので、なんでも、できちゃう。

<project name="SimpleTest" basedir="." default="wt.full">

<property
    name="webtest.home"
    location="C:\user\sandbox\webtest\webtest"
/>

<import file="${webtest.home}/webtest.xml"/>

<target name="wt.testInWork">

    <webtest name="Simple Test">
    <config browser="FF3"/>

    <enableJavaScript description="JavaScriptの文法ミスを回避" enable="false" />

    <invoke url="http://chinamall.yahoo.co.jp/"/>

    <setInputField name="q" value="腕時計"/>
    <clickButton label="検索"/>

    <groovy description="スクリプト">

        def file = new File( 'work.dat' )
        def document = step.context.currentResponse.documentElement

        document.getHtmlElementsByAttribute('li','class','decName').each
        {
            def data = it.getHtmlElementsByTagName( 'a' ).get(0).getTextContent()
            // println data
            file.append( data + '\n' ) 
        }
    </groovy>

    </webtest>

</target>
</project>

Groovy で、外部コマンドの実行

あまり Groovy ぽくないかな?

// コマンドと引数の組立

ProcessBuilder builder = new ProcessBuilder( "ping", "-n", "2", "localhost" ) ; 

// 作業ディレクトリの指定 ( デフォルトで Java IO の作業ディレクトリ を指定)

File workingDirectory = new File( System.getProperty("java.io.tmpdir" ) ) ; 
builder.directory( workingDirectory ) ;

// 実行して、結果を取得

Process process = builder.start();
process.waitForOrKill( 10 * 1000 ); // 10秒のコマンド実行タイムアウトの設定
int exitValue = process.exitValue(); // 終了コード 取得

// 実行結果の判定と後処理

if(exitValue == 0 )
{
    // 成功時の処理
    // 実行内容を表示 (ロギングされる?)
    println ( "SUCCESS " + new Date() );
    println( builder.command() ) ;
    println( process.text ) ; // 実行結果
}
else
{
    // 失敗時の処理
    // 実行コマンド、ディレクトリ、環境変数を、エラーメッセージとして組み立てる

   user = System.getProperty( "user.name" ) ;    
   command = builder.command() ;
   workingDirectory = builder.directory() ; 
   environment = builder.environment();
  
   message = "\nuser=" + user ;
   message += "\nexit code=" + exitValue;
   message += "\ncommand=" + command;
   message += "\ndirectory=" + workingDirectory;
   message += "\nEnvironment:" ;

   for( each in environment )
   {
       message += "\n" + each;
   }
   
   // 例外をスロー
   throw new Exception( message );
}

Mule smtp outbound で、サーバーとの接続障害

Mule ESB で、qmail サーバーに、メール送信 outbound で、接続エラーが発生。

qmail サーバー側で、アイドルコネクションを切断しているのを、smtp コネクタがうまく対応できない。

接続があると思って送信するので、SMTPSendFailedException 例外発生。(そのメッセージはロスト)

ほんとうは、ちゃんと、Retry ポリシーを組み込むべきだろうけど、とりあえず、例外処理用のルーターを使って、お手軽リトライを設定。

...
<outbound>
  <exception-based-router>
    <smtp:outbound-endpoint host="localhost" port="25" />
    <smtp:outbound-endpoint host="localhost" port="25" />
  </exception-based-router>
</outbound>
...

これで、例外発生→再接続→送信OK になる。
後で、ちゃんとリトライポリシー組み込むこと。

Mule ESB は、大量のメッセージ処理を想定しているから、コネクションは、はりっぱなし、という思想なんだろうなあ。
たしかに、常時、大量のメッセージが流れている場合は、それでいいんだけど、一時間に数件あるかないか、というイベントもあるわけで、その場合、この smtp コネクタの動作は、具合が悪い。

Mule ESB は非同期メッセージングの並行処理

Mule ESB を、実際に使い始めているけど、なかなか、発想の転換が難しい、というのが実感。

inbound(受信) -> サービスコンポーネント -> outbound(送信)

という単純な仕組みを 連結していくだけなんだけどなあ。

手続き的に書きたくなる【アンチパターン

最初の頃、ひっかかったのは、「手続き的」に設計する、アンチパターン

個別のメッセージの処理、というより、「メッセージの集合」から、一件ずつ、順番に取り出しながら、処理していく、というバッチ処理の発想から抜け出せない。

並行処理

いま、苦戦しているのが、並行処理。
Mule ESB は、デフォルトで、

inbound したあとのメッセージ処理 4つのスレッドで並列処理
サービスコンポーネントの活性化 4つのスレッドで並列処理
outbound 4つのスレッドで並列処理

という方式になっている。

メッセージの到着順と、処理の順番、前後関係は、なにも保証されてない。
これを、前提とした、設計の頭になかなかきりかわらない。

あと、データベースへのコネクションが並列して発生するとか、わかってしまえば、あたりまえなんだけど、直感的には、単独の処理プロセスのイメージからヌケきれず、かなりつまづいている。

Mule ESB 自体は、かなり、軽量で、高トラフィックのメッセージ処理ができる手ごたえがある。
ところが、大量のメッセージを高速に並列処理できる Mule ESB のよさが、データベースアクセス、ファイルの読み書き、SMTP でのメール送受信とかがからんでくると、とたんに性能問題、容量問題、データや処理の一貫性問題にぶつかる。

現場で格闘しながら、必死に勉強中です。
サンプルプログラムみたいに、動いたら、おしまい、というわけにはいかない。

こっちの日記では、もっと、コードよりのネタを書く

jugem の方で、システム設計日記を、書いているが、あっちは、モデリングや設計のネタ。

こっちでは、もっと、どろどろした、実装ネタをちょこちょこメモっていきたい。

とりあえずは、最近はまっている、Mule ESB ネタが中心になるかな。

jugem より、こっちのほうが、ソースコードとか、書きやすそうだし。