使用 .NET 撰寫 IBM WebSphere MQ 應用程式

這不是一份「看完就會」的入門教學文件。但如果你是 .NET 開發人員,從沒碰過 WebSphere MQ,卻要與之整合,那麼這篇文章或許會有點幫助。

基礎觀念

佇列就是先進先出,就像排隊。很簡單的概念,也很實用。

情境:你這頭丟一個訊息到佇列裡,另一頭有人接收並進行後續處理;處理完後,可能就沒你的事了,這是一種應用場合。另一種可能的情況是,另一頭也會丟一個回應的訊息到佇列中,供你的程式接收。這樣一來一往,就很像是 HTTP 的請求-回應模型了--但它是非同步的,因為佇列伺服器雖然保證使命必達(訊息一定送到目的地),但並不保證何時送達。

如果要實作請求-回應模型,就得處理一些細節問題。比如說,進入佇列的訊息可能很多,你怎麼知道哪一條才是對方回應給你的訊息?又如,有時候會需要訂閱某個佇列的訊息,亦即當特定佇列有任何新訊息進入時,就要通知你的程式。

概念很簡單,實際應用起來卻也有不少東西要處理。

入門指引

當初在學寫 WebSphere MQ 的應用程式時,我並沒有整理自己的筆記,所以現在只能憑記憶提供一些入門的線索了。

我記得剛開始就算只是想寫個最簡單的取佇列訊息的程式都感到困難,因為其實還有一些 Websphere MQ 特定的觀念要了解,一些工具要知道怎麼用(MQ Explorer),還有一些術語要搞懂,例如 Queue Manager、Transmission Queue、Local Queue、Remote Queue 等等。這些關節若沒打通,就很容易卡在那裏,分不清該從何處下手。

IBM 網站上有份文件叫做 MQSeries Primer,對於了解基本觀念蠻有幫助。
糗事:先前我就曾搞過一個烏龍,以為把訊息塞進佇列時,只要有設定 MQ 物件的 Correlation ID 屬性,等到對方(位於佇列另一頭的服務程式)處理完畢,要丟回傳訊息給我時,就一定也會在回傳訊息的物件裡面設定好相同的 Correlation ID,好讓我透過篩選的方式直接從佇列中抓出對應的訊息(就不用逐一檢視每個訊息)。抱著這個錯誤的信念,試過各種寫法,都無法取得對應的訊息,不禁懷疑 IBM WebSphere MQ 的 .NET 類別是不是有問題,為何它提供的訊息篩選機制都無法運作?最後經網路論壇上的幾位前輩點破,才恍然大悟:訊息篩選必須要另一頭的伺服器端應用程式也配合寫入 Correlation ID 屬性才行。我在與另一套現成的 MQ 應用程式溝通時,一開始就認定對方一定也有配合實作 MQ 物件層級的篩選機制,於是在錯誤的前提下往錯誤的方向努力不懈,白白浪費許多時間。

大致了解 IBM Websphere MQ 的基本運作方式與各種佇列的用途之後,可以到 IBM 網站下載 IBM Websphere MQ v7.x 試用版。裝好之後,程式集裡面會有一個 IBM WebSphere MQ Explorer。你可以先玩一下這個工具,用它來建立 Queue Manager、Queue 等物件(Channels 則可暫且略過)。此工具還有提供基本的測試功能,例如將測試訊息丟入指定的佇列。這樣你就可以先專注在取訊息的動作,省掉一些麻煩。

然後,我會建議花點時間研究這篇文章與其範例程式:IBM WebSphere MQ with C#: GUI application that is both GET REQUEST/ PUT RESPONSE and PUT REQUEST/ GET RESPONSE

搭配 WebSphere MQ Explorer 的使用,應該就可以做些簡單的測試了。其他還記得的有:
  • 你的 .NET 專案至少要加入這個 DLL 組件參考:amqmdnet.dll。當你裝了 WebSphere MQ v7.x,就可以在 Program Files (x86)\IBM\WebSphere MQ\bin\ 底下找到這個檔案。
  • 你的 .NET 專案可以使用 .NET Framework 4 來編譯。

如果剛才那篇技術文章的範例程式,你已經可以實作八九成,那應該差不多算是入門了,也就可以接著看事件觸發的部分。

事件觸發

網路上能夠找到的 .NET 範例(例如前面提到的那篇文章),大都是以主動詢問(poll)的方式來取得佇列中的訊息。在沒有新訊息進來的情況下,我們的程式會一直等在那裡,直到超過  MQGetMessageOptions 物件的 WaitInterval 屬性所設定的時間才會放棄。或者,WaitInterval 也可以設定成 MQC.MQWI_UNLIMITED(等到天荒地老)。

但有時候,我們不想以 polling 這種不斷詢問的方式來取得佇列中的訊息,而希望每當有新訊息丟進佇列時,我們的程式就要收到通知。儘管這類需求很普遍,MQ .NET 類別卻沒有「原生」支援訊息通知的事件訂閱模型,亦即無法單單用 .NET 類別就完成事件訂閱和通知等動作。

不過,它有提供外部工具來達到同樣效果--MQ .NET Monitor

MQ .NET Monitor 的使用方式大致如下:
  1. 建立一個 .NET 類別庫專案,並加入兩個 IBM Websphere MQ 的組件參考:amqmdnet.dll 和 amqmdnm.dll。
  2. 在此專案中加入一個類別,此類別須實作 IMQObjectTrigger 介面。該介面只有一個方法:
    void Execute(MQQueueManager qmgr, MQQueue queue, MQMessage message, string param);
    
  3. 將你要處理訊息的邏輯寫在 Execuet 方法中,然後 build 你的組件。
  4. 開始監聽(攔截)佇列訊息。作法是執行命令列工具: runmqdnm,這會啟動 .NET Monitor。runmqdnm 需要指定佇列管理員、佇列名稱、你的組件檔案名稱與類別名稱等參數,用法可參考稍後的範例。
  5. 現在只要佇列有新訊息進來,你的 .NET 類別的 Execute 方法就會被觸發。
  6. 不需要接收新訊息時,要執行 endmqdnm 來停止 .NET Monitor。

聽起來有點不牢靠的感覺,但實際試過之後,並沒有發現什麼大問題。底下是一個很簡單的範例,它會在每次收到訊息通知時,將訊息內容輸出至 C:\log.txt。
using IBM.WMQ;
using IBM.WMQMonitor;


namespace MQEventDemo
{
    public class MQMessageListener : IMQObjectTrigger
    {
        public void Execute(MQQueueManager qmgr, MQQueue queue, MQMessage message, string param)
        {
            System.IO.File.WriteAllText(@"C:\log.txt", message.ReadString(message.MessageLength));
        }
    }
}
注意:即使你的 Execute() 方法沒有做任何事,佇列中的訊息還是已經被取出來了,也就是說,佇列裡面沒有那筆訊息了。

要開始攔截訊息,就執行 runmqdnm。指令如下:

runmqdnm -m MyQueueManager -q MyLocalQueue -a MQEventDemo.dll -c MQEventDemo.MQMessageListner

若一切順利,畫面上應該不會看到任何訊息,而就只是停在那裡持續監聽佇列。如下圖:

圖 1:runmqdnm

其中的 MyQueueManager 和 MyLocalQueue 都是利用 WebSphere MQ Explorer 來建立的,參考下圖:

圖 2:WebSphere MQ Explorer

你可以在圖中右邊區塊的 MyLocalQueue 上點右鍵,選「放置測試訊息」,然後到 C:\ 根目錄下查看 log.txt 的檔案內容,裡面應該會有你剛剛丟進去的測試訊息。如果在執行 runmqdnm 之前,欲攔截的佇列裡面就已經存在訊息,等到執行 runmqdnm 時,這些既有訊息都會被逐一取出並呼叫你的類別的 Execute 方法。

若要停止監聽,可在圖 1 的命令列視窗中按 Ctrl+C 以終止該程式,或者也可以開啟另一個命令視窗,輸入以下指令:

endmqdnm -m MyQueueManager -q MyLocalQueue

這樣也會結束先前的 runmqdnm 程式。

底下這個短片,就是前面的範例程式的執行過程(畫質請切換到 720p,不然會太糊)。



最後列出幾個需要注意的地方:

  • 用來攔截佇列訊息的 .NET 組件必須是 DLL 組件,不可以是 EXE。
  • MQ .NET Monitor 會以非同步的方式呼叫你的觸發器類別的 Execute 方法,所以不要以為你的 Excute 方法是每處理完一個訊息才會接著下一個訊息。
  • 每一次有攔截到新進來的佇列訊息時,MQ .NET Monitor 就會建立你的觸發器類別的 instance。如果你希望有些初始化的動作只想要執行一次,可以放在類別的 static constructor 裡面。
  • 你的 .NET 組件必須使用 .NET Framework 2.0。若使用 .NET Framework 4,在執行 runmqdnm 時會報錯:
    AMQ8378: 從 .NET Framework 'Could not load file or assembly 'file:///MyMQEventDemo.dll' or one of its dependencies. This assembly is built by a runtime newer than the currently loaded runtime and cannot be loaded.' 收到非預期的異常狀況
  • 欲監看的佇列,其佇列類型必須是本地端佇列(local queue),而不可以是遠端佇列( remote queue),而且它必須是正常佇列(normal queue),而不可以是傳輸佇列(transmission queue)。若違反上述規則,在執行 runmqdnm 時會出現錯誤:
    AMQ8377: 應用程式已收到非預期的錯誤 2042 (或 2045)。

如果不喜歡這種倚賴外部工具來攔截訊息的作法,其實也可以用 polling 的方式,用一個迴圈來持續抓取特定佇列的訊息,以實作自己的訊息通知機制。實際寫法可參考這兩篇文章:


小結

看看 IBM 網站上提供的幾本電子書,就會知道光用一篇部落格文章就要把 WebSphere MQ 與其 .NET 類別的用法講完是很難辦到的--我也沒有那個企圖。如果你是 .NET 開發人員,從沒碰過 WebSphere MQ,卻要與之整合,那麼這篇文章或許能夠提供一些方向,減少一些摸索和嘗試錯誤的時間,那也就夠了。

P.S. 往後我不見得有機會再深入研究 WebSphere MQ,有問題的朋友,建議至官方論壇爬文或發問,會更快獲得專業解答喔!

沒有留言:

技術提供:Blogger.
回頂端⬆️