使用 Java API 處理 WebSphere MQ 大訊息

CloudSpace發表於2009-04-09

訊息分片

訊息分片的做法是把應用上一個大的邏輯訊息分割成一個一個小的片段,每一個片段作為一個 WebSphere MQ 訊息獨立傳輸,通過 MQMD 中 GroupId、 MsgSeqNumber 和 Offset 3 個屬性來標識,起始訊息的 Offset 值為 0,而最後一個訊息則會使用如下標記標識這是最後一個片段:MQMF_LAST_SEGMENT。

具體從實現上來說,訊息分片可以細分為兩種模式:一是由佇列管理器自動實現訊息的分片和組裝;二是由應用程式來實現訊息的分片和組裝。下面我們將詳細向您介紹這兩種實現方式。

佇列管理器自動實現的訊息分片

顧名思義,佇列管理器自動實現的訊息分片就是由佇列管理器來完成訊息的分片和組裝。對應用程式來說,不管是傳送方還是接收方,它所處理的還是一個完整的大訊息,只是在程式中通過設定一些標識來指示佇列管理器切分訊息後再傳輸。所以,這種方式適用的場合為,出於傳輸效率的考慮,WebSphere MQ 不適宜傳輸大訊息,而應用程式可以處理大訊息,允許佔用大塊記憶體。而且,此種方式對編寫應用程式來說也比較簡單。

實際在使用 Java API 程式設計時,對於傳送方,傳送訊息時需要設定訊息的 messageFlags 如下:

Msg.messageFlags = MQC.MQMF_SEGMENTATION_ALLOWED;

對於接收方,接收訊息時需要設定 MQGetMessageOptions:

MQGetMessageOptions gmo = new MQGetMessageOptions ();

gmo.options = MQC.MQGMO_COMPLETE_MSG;

佇列管理器自動實現訊息分片的部分程式碼如清單 1,您可以下載詳細的示例程式程式碼。


清單 1 佇列管理器自動實現訊息分片
QMgrSegSender.java:
    int penOptions = MQC.MQOO_OUTPUT | MQC.MQOO_FAIL_IF_QUIESCING;
    myQMgr = new MQQueueManager ("QM1");
    myQueue = myQMgr.accessQueue("TESTQ", openOptions);
    MQMessage myMsg = new MQMessage ();
    myMsg.messageFlags = MQC.MQMF_SEGMENTATION_ALLOWED;
    MQPutMessageOptions pmo = new MQPutMessageOptions ();
    String strMsg = "";
    for (int i=0;i<=100;i++)
        strMsg = strMsg + "Hello";
    myMsg.write(strMsg.getBytes());
    myQueue.put(myMsg,pmo);
    System.out.println("Put message:\n" + strMsg);
    myQueue.close();
    myQMgr.disconnect();
QMgrSegReceiver.java:
    int penOptions = MQC.MQOO_INPUT_SHARED | MQC.MQOO_FAIL_IF_QUIESCING;
    myQMgr = new MQQueueManager ("QM1");
    myQueue = myQMgr.accessQueue("TESTQ", openOptions);
    MQMessage myMsg = new MQMessage ();
    MQGetMessageOptions gmo = new MQGetMessageOptions ();
    gmo.options = MQC.MQGMO_COMPLETE_MSG; 
    myQueue.get(myMsg, gmo);
    byte[] b = new byte[myMsg.getMessageLength()];
    myMsg.readFully(b);
    String strMsg = new String(b);
    System.out.println("Got message:\n" + strMsg);
    myQueue.close();
    myQMgr.disconnect();

程式功能介紹:

(1)QMgrSegSender 程式是構造一個長度為505位元組的訊息並把它寫入佇列 TESTQ 中。

為使 MQ 不能傳輸505位元組的訊息,可以修改佇列 TESTQ 的屬性“最大訊息長度(MAXMSGL)”為100。

執行結果如下圖 1 所示,該訊息被佇列管理器自動分割成6個片段訊息:


圖 1 在 WebSphere MQ 資源管理器中瀏覽分片訊息

(2)QMgrSegReceiver 程式是從佇列 TESTQ 中讀取一個訊息。

我們觀察執行的結果是它把佇列中6個片段訊息組成一個完整的大訊息取出。

使用佇列管理器自動實現訊息分片對應用開發人員來講比較簡單,但是需要確保程式在記憶體使用等方面可以處理完整的大訊息。

應用程式實現的訊息分片

應用程式實現訊息分片是指,在傳送方應用程式中事先把大訊息切分成多個片段,每一個片段作為一個獨立的訊息,寫入到佇列中;在接收方應用程式中,每一個片段作為一個獨立的訊息被取出,由程式把所有的訊息片段組裝成一個完整的訊息。這種模式適用的場合為,WebSphere MQ 和應用程式兩者都不方便處理這麼大的單個訊息。

一般在傳送方程式實現中,我們是把所有的訊息片段放在一個同步點中傳送,所以需要設定 MQPutMessageOptions 為 MQPMO_SYNCPOINT;同時,我們推薦使用選項 MQPMO_LOGICAL_ORDER,這意味著佇列管理器自動維護每個訊息片段的偏移量(Offset),否則,需要應用程式自身來設定:

MQPutMessageOptions pmo = new MQPutMessageOptions ();

pmo.options = MQC.MQPMO_LOGICAL_ORDER + MQC.MQPMO_SYNCPOINT;

對於每一個訊息片段,我們還應標識這是一個訊息片段(MQMF_SEGMENT):

myMsg.messageFlags = MQC.MQMF_SEGMENT;

對於最後一個訊息片段,也需要設定特殊標識(MQMF_LAST_SEGMENT):

myMsg.messageFlags = MQC.MQMF_LAST_SEGMENT;

同樣的,在接收方程式中,我們也是把所有的訊息片段放在一個同步點中接收,所以需要設定 MQGetMessageOptions 為 MQGMO_SYNCPOINT;同時,我們也設定 MQGMO_LOGICAL_ORDER 來保證所有的訊息片段是按邏輯順序被取出;另外,我們還需設定所有的訊息片段都到達後才處理的選項(MQGMO_ALL_SEGMENTS_AVAILABLE),這是為了防止萬一由於異常導致訊息片段丟失而引起程式無限等待的情形:

MQGetMessageOptions gmo = new MQGetMessageOptions ();

gmo.options = MQC.MQGMO_LOGICAL_ORDER + MQC.MQGMO_SYNCPOINT + MQC.MQGMO_ALL_SEGMENTS_AVAILABLE;

由於我們是按邏輯順序來取訊息片段的,所以設定迴圈取訊息的時候,只要遇到某一個訊息片段是最後一個的標識,我們就認為已經取到了完整的訊息。如果沒有設定按照邏輯順序來取訊息片段,則需要應用程式根據訊息序列號、偏移量、是否是最後一個訊息片段等標識來判斷是否已經取到完整的訊息。

應用程式實現訊息分片的部分程式碼如清單 2,您可以下載詳細的示例程式碼:


清單 2 應用程式實現訊息分片
AppSegSender.java
    int penOptions = MQC.MQOO_OUTPUT | MQC.MQOO_FAIL_IF_QUIESCING;
    myQMgr = new MQQueueManager ("QM1");
    myQueue = myQMgr.accessQueue("TESTQ", openOptions);
    for(int i=0;i<3;i++)
    {
        MQMessage myMsg = new MQMessage ();
        MQPutMessageOptions pmo = new MQPutMessageOptions ();
        pmo.options = MQC.MQPMO_LOGICAL_ORDER + MQC.MQPMO_SYNCPOINT;
        if (i<2)
            myMsg.messageFlags = MQC.MQMF_SEGMENT;
        else
            myMsg.messageFlags = MQC.MQMF_LAST_SEGMENT;
        String strMsg = "Hello" + i;
        myMsg.write(strMsg.getBytes());
        myQueue.put(myMsg,pmo);
        System.out.println("Put message '" + strMsg + "'! ");
    }
    myQMgr.commit();	
    myQueue.close();
    myQMgr.disconnect();
AppSegReceiver.java
    int penOptions = MQC.MQOO_INPUT_SHARED | MQC.MQOO_FAIL_IF_QUIESCING;
    myQMgr = new MQQueueManager ("QM1");
    myQueue = myQMgr.accessQueue("TESTQ", openOptions);
    MQMessage myMsg;
    MQGetMessageOptions gmo = new MQGetMessageOptions ();
    gmo.options = 
    MQC.MQGMO_LOGICAL_ORDER + MQC.MQGMO_SYNCPOINT + MQC.MQGMO_ALL_SEGMENTS_AVAILABLE;
    String strMsg = "";
    boolean isLastSegment = false;
    while(!isLastSegment)
    {
        myMsg = new MQMessage ();
        myQueue.get(myMsg, gmo);
        if (myMsg.messageFlags == MQC.MQMF_SEGMENT + MQC.MQMF_LAST_SEGMENT)
            isLastSegment = true;
        byte[] b = new byte[myMsg.getMessageLength()];
        myMsg.readFully(b);
        strMsg += new String(b);
    }
    System.out.println("Got message:\n" + strMsg);
    myQMgr.commit();
    myQueue.close();
   myQMgr.disconnect();

程式功能介紹:

  1. AppSegSender 程式是使用一個 for 迴圈,構造一個完整訊息的三個訊息片段,分別寫入佇列 TESTQ 中。
  2. AppSegReceiver 程式是從佇列 TESTQ 中迴圈讀取訊息片段,根據其邏輯順序以及是否是最後一個訊息片段來組裝完整的訊息。

相對於佇列管理器器自動實現訊息分片的方式,應用程式實現訊息分片略顯複雜,但是它能夠處理更大的訊息。






訊息分組

從實現手段上來講,訊息分組和訊息分片非常類似,但二者有著完全不同的業務意義。在訊息分片中,雖然每一個訊息片段都作為一個獨立的訊息進行傳輸,但只有收集到所有的訊息片段組成一個完整的訊息之後才有業務意義,單獨的一個訊息片段是沒有任何業務意義的。從這一點上講,我們是由於技術上處理大訊息有困難,才想到把大訊息進行切分的。而訊息分組則不同,它的每一個成員訊息都是一個具有業務意義的獨立訊息,只是由於某些需要,比如,組內訊息有明確的先後順序,等等,才把這批訊息作為一組進行傳輸。

在實際實現中,組內的訊息是通過 MQMD 中 GroupId 和 MsgSeqNumber 2個屬性來標識,而最後一個訊息則會標記這是組內的最後一個訊息(MQMF_LAST_MSG_IN_GROUP)。

與訊息分片類似,一般在傳送方程式中,我們是把同一組的所有訊息放在一個同步點中傳送,所以需要設定 MQPutMessageOptions 為 MQPMO_SYNCPOINT;同時,我們推薦使用選項 MQPMO_LOGICAL_ORDER,這意味著佇列管理器自動維護每個訊息的序列號(MsgSeqNumber),否則,需要應用程式自身來設定:

MQPutMessageOptions pmo = new MQPutMessageOptions ();

pmo.options = MQC.MQPMO_LOGICAL_ORDER + MQC.MQPMO_SYNCPOINT;

對於每一個訊息,我們還應標識這是一個組內的訊息(MQMF_MSG_IN_GROUP):

myMsg.messageFlags = MQC.MQMF_MSG_IN_GROUP;

對於組內的最後一個訊息,也需要設定特殊標識(MQMF_LAST_MSG_IN_GROUP):

myMsg.messageFlags = MQC.MQMF_LAST_MSG_IN_GROUP;

同樣的,在接收方程式中,我們也是把同一組的所有訊息放在一個同步點中接收,所以需要設定 MQGetMessageOptions 為 MQGMO_SYNCPOINT;同時,我們也設定 MQGMO_LOGICAL_ORDER 來保證同一個組裡的所有訊息是按邏輯順序被取出;另外,我們還需設定同一組所有的訊息都到達後才處理的選項(MQGMO_ALL_MSGS_AVAILABLE),這是為了防止萬一由於異常導致某一成員訊息丟失而引起程式無限等待的情形:

MQGetMessageOptions gmo = new MQGetMessageOptions ();

gmo.options = MQC.MQGMO_LOGICAL_ORDER + MQC.MQGMO_SYNCPOINT + MQC.MQGMO_ALL_MSGS_AVAILABLE;

由於我們是按邏輯順序來取組內成員訊息的,所以設定迴圈取訊息的時候,只要遇到某一個訊息是組內最後一個的標識,我們就認為已經取到了該組所有的訊息。如果沒有設定按照邏輯順序來取訊息片段,則需要應用程式根據訊息序列號、取到的訊息個數、是否是組內最後一個訊息等標識來判斷是否已經取到該組所有的訊息。

部分程式碼如清單 3,您可以下載詳細的示例程式碼。


清單 3 訊息分組
AppGrpSender.java
    int penOptions = MQC.MQOO_OUTPUT | MQC.MQOO_FAIL_IF_QUIESCING;
    myQMgr = new MQQueueManager ("QM1");
    myQueue = myQMgr.accessQueue("TESTQ", openOptions);
    for(int i=0;i<3;i++)
    {
        MQMessage myMsg = new MQMessage ();
        MQPutMessageOptions pmo = new MQPutMessageOptions ();
        pmo.options = MQC.MQPMO_LOGICAL_ORDER + MQC.MQPMO_SYNCPOINT;
        if (i<2)
            myMsg.messageFlags = MQC.MQMF_MSG_IN_GROUP;
        else
            myMsg.messageFlags = MQC.MQMF_LAST_MSG_IN_GROUP;
        String strMsg = "Hello" + i;
        myMsg.write(strMsg.getBytes());
        myQueue.put(myMsg,pmo);
        System.out.println("Put message" + (i+1) + " '" + strMsg + "'! ");
    }
    myQMgr.commit();
    myQueue.close();
    myQMgr.disconnect();
AppGrpReceiver.java
    int penOptions = MQC.MQOO_INPUT_SHARED | MQC.MQOO_FAIL_IF_QUIESCING;
    myQMgr = new MQQueueManager ("QM1");
    myQueue = myQMgr.accessQueue("TESTQ", openOptions);
    MQMessage myMsg;
    MQGetMessageOptions gmo = new MQGetMessageOptions ();
    gmo.options = 
    MQC.MQGMO_LOGICAL_ORDER + MQC.MQGMO_SYNCPOINT + MQC.MQGMO_ALL_MSGS_AVAILABLE;
    String strMsg = "";
    boolean isLastMsg = false;
    int seq = 0;
    while(!isLastMsg)
    {
        seq++;
        myMsg = new MQMessage ();
        myQueue.get(myMsg, gmo);
        if (myMsg.messageFlags == MQC.MQMF_MSG_IN_GROUP + MQC.MQMF_LAST_MSG_IN_GROUP)
            isLastMsg = true;
        byte[] b = new byte[myMsg.getMessageLength()];
        myMsg.readFully(b);
        strMsg = new String(b);
        System.out.println("Got message" + seq + ":\n" + strMsg);
    }
    myQMgr.commit();
    myQueue.close();
    myQMgr.disconnect();

程式功能介紹:

  1. AppGrpSender 程式是使用一個 for 迴圈,構造一個組的三個訊息,分別寫入佇列 TESTQ 中。
  2. AppGrpReceiver 程式是從佇列 TESTQ 中迴圈讀取訊息,根據其邏輯順序以及是否是組內最後一個訊息來判斷是否已取完同一組內的所有訊息。

相對於訊息分片,訊息分組不僅僅是處理大訊息的一種方法,更為重要的是,訊息分組還能維護一組業務資料中的邏輯關係。







結束語

訊息分片和訊息分組是在 WebSphere MQ 的程式設計中處理大訊息的常用手段,到底採用哪種方式比較合適,需要根據實際的需求而定。如果大訊息需要分割成有實際業務意義的一批小訊息,那麼採用訊息分組比較合適;反之,如果大訊息無法分割成有實際業務意義的小訊息,那麼就採用訊息分片。甚至在某些複雜的場合下,訊息分片和訊息分組可以結合起來使用,比如,某批訊息傳輸時由於有先後順序的要求,被歸併到一個組內,同時由於部分訊息比較大,又需要分片傳輸,有興趣的讀者可以自己來實現一下這個複雜的場景。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/14789789/viewspace-586926/,如需轉載,請註明出處,否則將追究法律責任。

相關文章