使用 Java API 处理 WebSphere MQ 大消息

消息分片

消息分片的做法是把应用上一个大的逻辑消息分割成一个一个小的片段,每一个片段作为一个 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/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/14789789/viewspace-586926/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值