转自:http://www.ibm.com/developerworks/cn/websphere/library/techarticles/0902_yangj_mq/
本文介绍了WebSphere MQ中处理大消息的两种方法:消息分片和消息分组,并配以消息的示例代码演示了如何使用.NET API来实现消息分片和消息分组.
WebSphere MQ 中处理大消息的方法
WebSphere对处理的单条消息的大小事有限制的,目前支持的最大消息是100M,而且随着消息大小的增大,WebSphere MQ处理的性能也会随之下降,从最佳实践来说,WebSphere MQ传输大小为几K的消息其效率是最高的.那如何使WebSphere MQ 能高效的处理大消息呢?
WebSphere MQ提供了处理大消息的两种方法:消息分片和消息分组,下面我们来看在使用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,您可以下载详细的示例程序代码。
QMgrSegSender.java: int openOptions = 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 openOptions = 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。
(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,您可以下载详细的示例代码:
AppSegSender.java int openOptions = 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 openOptions = 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();
程序功能介绍:
相对于队列管理器器自动实现消息分片的方式,应用程序实现消息分片略显复杂,但是它能够处理更大的消息。
从实现手段上来讲,消息分组和消息分片非常类似,但二者有着完全不同的业务意义。在消息分片中,虽然每一个消息片段都作为一个独立的消息进行传输,但只有收集到所有的消息片段组成一个完整的消息之后才有业务意义,单独的一个消息片段是没有任何业务意义的。从这一点上讲,我们是由于技术上处理大消息有困难,才想到把大消息进行切分的。而消息分组则不同,它的每一个成员消息都是一个具有业务意义的独立消息,只是由于某些需要,比如,组内消息有明确的先后顺序,等等,才把这批消息作为一组进行传输。
在实际实现中,组内的消息是通过 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,您可以下载详细的示例代码。
AppGrpSender.java int openOptions = 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 openOptions = 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();
程序功能介绍:
相对于消息分片,消息分组不仅仅是处理大消息的一种方法,更为重要的是,消息分组还能维护一组业务数据中的逻辑关系。
消息分片和消息分组是在 WebSphere MQ 的编程中处理大消息的常用手段,到底采用哪种方式比较合适,需要根据实际的需求而定。如果大消息需要分割成有实际业务意义的一批小消息,那么采用消息分组比较合适;反之,如果大消息无法分割成有实际业务意义的小消息,那么就采用消息分片。甚至在某些复杂的场合下,消息分片和消息分组可以结合起来使用,比如,某批消息传输时由于有先后顺序的要求,被归并到一个组内,同时由于部分消息比较大,又需要分片传输,有兴趣的读者可以自己来实现一下这个复杂的场景。