2012年5月28日月曜日

Powershellでアクセスログ統計作成(MSMQとジョブ版)その1

Awk好き技術者の書いたPowerShell

背景

 以前のブログでWEBサーバのアクセスログ統計を取るスクリプトを作成しましたが、処理時間の問題がありました。サーバログを1日単位等でバッチ的に処理をするのは時間的に厳しく、PowerShellが適用できない場合がありそうということです。今回はその続きとして発想を変えてみたいと思います。(なにもそこまでPowerShellに肩入れすることはないのですが、乗りかかった船なのでちょっとだけ。。)
  • バッチ処理ではなく、常駐型にする。(特に重い部分だけでも随時処理しておけば使用に耐えうる。)
  • 最近はWebサーバが複数台構成になることが多いので、WEBサーバでできることはそれぞれのサーバで処理する。
  • 最終的な集計結果をまとめる処理は別のマシンで処理する。
  • サーバ間のデータの連携はWindowsServerに標準搭載されるMicrosoftMessageQueuing(MSMQ)を使用する。
 ま、簡単に言うと個人的にMSMQとPowerShellのジョブの機能を使ってみたかっただけです。
 いきなり全部やろうとすると、うまくいかないので段階的に進めていきます。

お題(ステップ1)

 シングルサーバ構成で良いので、MSMQを使ってプログラム間でログ的なオブジェクトを引き渡すようなプログラムを作成します。今回はログデータではなく単純なCSV形式のデータとします。基本的な流れは以下のとおり。
  1. MSMQ作成
  2. MSMQへのメッセージ送信
  3. MSMQからのメッセージ受信
  4. MSMQ削除
[void][Reflection.Assembly]::LoadWithPartialName("System.Messaging")

$quename=".\private$\SampleQueue"
$musicians=@"
name,part,type
Joao Girlberto,Guitar,Genius
U-Zhaan,Tabla,Well-Trained,
Jeff Beck,Guitar,Genius
吾妻光良,Guitar,Well-Trained
Kassin,Bass,Talented
Harry Hosono,Bass,No Type
"@

"==================================================================="
if ([System.Messaging.MessageQueue]::Exists($quename)){
  "delete old queue"
  [System.Messaging.MessageQueue]::Delete($quename)
}

"create a new queue"
$q = [System.Messaging.MessageQueue]::Create($quename)

$q.SetPermissions("everyone", 
    [System.Messaging.MessageQueueAccessRights]::FullControl,            
    [System.Messaging.AccessControlEntryType]::Set)

"==================================================================="
$q2 = New-Object System.Messaging.MessageQueue $quename

$msg = New-Object System.Messaging.Message "SampleMessage"
$msg.label = "Sample Message"

"Send Messages to MSMQ"
$musicians|
    convertFrom-csv|
    % { convertTo-xml $_ }|
    % {
        $msg.body=$_
        "SEND: " + $msg.body.objects.object.property[0].innerText
        $q2.send($msg)
    }

"==================================================================="
function ConvertFrom-Xml($xml) {
    foreach ($object in @($xml.objects.object)) {
        $PSObject = New-Object PSObject
        foreach ($property in @($object.property)) {
            $PSObject |
            Add-Member NoteProperty $property.name $property.innerText
        }
        $PSObject
    }
}

"Receive Messages from MSMQ"
$q3 = New-Object System.Messaging.MessageQueue $quename
$ts = New-Object TimeSpan 3e7    # wait timer 3 seconds

1..$q3.GetAllMessages().length |
%{
    $rmsg = $q3.receive($ts)
    
    #[System.Xml.XmlDocument]$xml=$rmsg.body    
    $rmsg.BodyStream.Position = 0
    $sr = New-Object System.IO.StreamReader( $rmsg.BodyStream )
    [System.Xml.XmlDocument]$xml=$sr.ReadToEnd()

    ConvertFrom-Xml $xml
}|
? {( $_.part -eq "guitar" )}|
Select name

"==================================================================="
"Delete Queue"
[System.Messaging.MessageQueue]::Delete($quename)

ちょっとだけ解説

  • MSMQはOSのデフォルト(今回はWindowsServer2008)ではインストールされないので、機能の追加が必要ですのでご注意ください。
  • キューの操作はSystem.Messaging.MessageQueueの該当メソッドを使用するだけなので比較的簡単です。
  • 入力データであるCSV形式のヒアドキュメントをimport-csvで読み込んで、XMLに変換してキューに登録しています。オブジェクトをそのままシリアライズするような方法が見当たらなかったので。でも、こうしておけばサーバマネージャから直接キューの内容が見れるのでかえって良かったです。(JSONでもできそう。)
  • オブジェクトをXMLに変換するコマンドレットConvertTo-XMLはありましたが、受信時に必要となる逆の機能が見つからなかったので簡単な関数ConvertFrom-XMLを作りました。
  • すべてのメッセージを受信したあと、パイプラインでWhereやSelectをやってみました。具体的には、ヒアドキュメントで定義されたミュージシャンのうちギターに該当する4名の名前が表示されます。


感想

  • MSMQの操作はあっけないぐらい簡単でした。ここれはスタンドアロン環境でやっていますが、複数サーバ間でもおそらく難しくないでしょう。
  • 一番苦労したのが、MSMQからのメッセージの取り出しの部分です。メッセージのボディ($rmsg.body)が$nullになってしまい、MessageQueue.FormatterとしてXMLやBinaryなどでやってもNGでした。けっきょく代替手段としてBodyStreamを使って読み込んでいます。何か良い方法はないのでしょうかね。
  • 今回は簡単なデータだったのでパフォーマンスのことは考えていません。
  • まずはCSVデータについて動いたので、次回はIISアクセスログで同様のことをチャレンジします。ただし、いつになるか不明ですが。

0 件のコメント:

コメントを投稿