级别: 初级 Dave Locke (locke@uk.ibm.com), 软件工程师, IBM Lakshman Yatawara (yatawal@uk.ibm.com), 高级 IT 专家, IBM
2006 年 2 月 22 日 随着移动设备应用的日趋广泛,许多使用 WebSphere MQ 和 WebSphere Message Broker 的组织开始使用 WebSphere MQ Everyplace 来将其消息传递网络延伸至涵盖移动设备。然而,针对 MQ 定义 MQ Everyplace 队列管理器带来了大量管理和可伸缩性挑战。本文为您介绍当涉及大量 MQ Everyplace 设备时如何简化 WebSphere MQ 的配置。
引言
随着移动设备应用的日趋广泛,许多使用 IBM® WebSphere® MQ(以下称为 MQ)和 IBM WebSphere Message Broker 的组织开始使用 WebSphere MQ Everyplace(以下称为 MQe)来将其消息传递网络延伸至涵盖移动设备。这些实例中的设备数量范围可以从几百到几万。实际的例子包括需要将消息发送到分布于 12 个不同国家的 16,000 名员工的后勤组织和拥有 1500 家商店的主要零售商。这两个企业都在中心站点使用 WebSphere MQ,而在端点使用 WebSphere MQ Everyplace。正常的方法要求将 MQ 配置为每个目标设备(MQe Queue Manager 和队列)都有一个远程队列定义。但是此方法导致需要定义和维护的 MQ 对象的数量多得不可接受,而且对操作人员来说管理是场恶梦。本文描述一种使得不需要创建 MQ 资源来寻址每台设备的解决方案。
WebSphere MQ / MQ Everyplace 体系结构
为了将消息从运行 MQe 的设备发送到 MQ,或者从 MQ 发送到一台设备,系统体系结构由三个组件构成。
- 设备队列管理器
- 网关队列管理器
- MQ 服务器
图 1. 典型的 MQ/MQe 体系结构
每个设备运行一个 MQe 队列管理器(以下称为设备队列管理器)。设备上的应用程序与设备队列管理器一起发送和接收往返队列的消息,它们既可以是设备上的本地队列,也可以是其他队列管理器(例如 MQ 服务器)上的远程队列。使用 MQ 服务器的应用程序可以发送去往设备队列管理器的消息。网关队列管理器方便了设备队列管理器和 MQ 服务器之间的消息交换。
网关队列管理器是一个配置有额外功能(称为 MQ 桥)的 MQe 队列管理器。对于从设备队列管理器到 MQ 服务器的消息,网关提供了一个穿透设备,它将消息从 compact MQe 格式转换成 MQ 格式。对于从 MQ 服务器到设备队列管理器的消息,网关提供存储转发设备来为当前未联机的设备保存消息。当设备联机时,设备队列管理器会将消息从存储转发设备提取出来。对于从设备队列管理器到 MQ 的消息,网关执行从 MQ 到 MQe 消息格式的逆向转换。
寻址远程 MQ Everyplace 设备
为了将消息从 MQe 传送到 MQ 或反之,必须在设备队列管理器、网关队列管理器和 MQ 服务器上配置诸如队列和连接等资源定义。本文重点介绍 MQ 服务器上必需的资源定义。
为了将消息发送到设备队列管理器,有许多配置 MQ 的方式。正常的做法要求每个设备队列管理器都有一个远程队列定义。
该远程队列定义是用目标设备队列管理器名称和队列名称定义的。为了完善从 MQ 到 MQe 的路径,远程队列与一个传输队列相关联,它提供存储转发设备来为网关队列管理器保存消息直到它准备接收为止。与从一个 MQ 服务器到另一个的通信不同,从 MQ 服务器到网关队列管理器的传输队列没有定义 MQ 通道。相反,网关队列管理器配置了一个侦听器,它从传输队列提取消息并维护到 MQe 目的地的确定传递过程。
使用此方法,每个设备上的每个队列都需要一个远程队列定义,因此远程队列定义的数量急剧增长。但是 MQ 有一个功能可以用于减少必需的定义数。当试图定位一个远程目的地时,MQ 有一个内置的搜索顺序:
- 与目标队列名称相同的远程队列定义
- 与目标队列管理器名称相同的远程队列定义
- 与目标队列管理器名称相同的传输队列
- 缺省的传输队列
您可以利用 MQ 搜索顺序并只按照远程队列定义来定义设备队列管理器名称。这个过程可以使设备上的任何队列都通过一个定义来寻址,从而将远程队列定义的数量降到与设备队列管理器的数量相同。
一种类似的方法是每个设备队列管理器使用一个传输队列。但是这种方法有几个明显的缺点。首先,网关队列管理器上的资源定义数量增长迅速,例如对于每个传输队列,都必须在网关上定义一个侦听器。其次,由于涉及到处理大量侦听器,所以网关的性能下降。
使用缺省传输队列一开始看起来使状况有所改善,因为它使得不需要远程队列定义。但是也有一些缺陷——任何没有相应的远程队列或传输队列的消息最终都被送到该缺省传输队列。如果没有目标设备队列管理器的消息最终都送到缺省传输队列,则网关队列管理器将试图传递该消息并失败。这种失败导致了额外的错误处理(消息无法传递),并可能中断消息从 MQ 到 MQe 的移动直到问题解决为止(有关更多细节,请参阅下面的未处理的消息)。只有一个网关队列管理器可以附带在一个传输队列中。因为每个 MQ 服务器只有一个缺省传输队列,所以只有一个网关队列管理器可以使用它。当需要多个网关时,这种限制减低了系统的可伸缩性。
有一个示例可以说明到目前为止所描述的不同方法的作用。给定这样的场景:每个设备队列管理器都有两个队列,而设备数量为 10,000。下面的列表显示每种方法需要的定义数(出于演示目的,在计算中使用了一个网关):
- 方法 1
- 设备队列管理器上的每个目标队列都有一个队列定义
MQ 定义数 = ( 2 q def ) * 10000 + 1 xmitq def = 20001
- 方法 2
- 每个目标设备队列管理器都有一个队列管理器定义
MQ 定义数 = ( 1 qmgr def ) * 10000 + 1 xmitq def = 10001
- 方法 3
- 每个目标设备队列管理器都有一个传输队列
MQ 定义数 = ( 1 xmitq def ) * 10000 = 10000
- 方法 4
- 使用缺省传输队列
MQ 定义数 = ( 1 xmitq def ) 1
图 2. MQ 配置方法
解决方案
到目前为止讨论的所有选择都有局限性,例如需要庞大的定义数、可伸缩性有限以及管理复杂。本文剩下的部分将描述一种解决方案,它解决了所有这些问题。该解决方案允许附带多个网关,从而消除单个传输队列的缺陷。同时,它使得不需要为每个设备队列管理器定义远程队列。此解决方案包括:
- 将消息发送到网关队列管理器,而不是目标队列管理器
- 对消息中的目标队列管理器和队列进行编码
- 拦截 MQ 传输队列和网关队列管理器之间的消息
- 替换 MQ 传输标头中的目标队列管理器和队列,以使网关不知道已发生过替换。替换是使用通道接收退出(MQ 客户端的一个功能)完成的。有关更多信息,请参阅 WebSphere MQ Using Java,转到第 7 章“Writing WebSphere MQ base Java applications”并查看“Writing User Exits”部分。
图 3. 解决方案体系结构
网关队列管理器侦听传输队列上的消息,并在准备转发到目标设备队列管理器的网关上接收它们。虽然消息是从 MQ 传送到 MQe 网关的,但实际的目的地细节(队列和队列管理器名称)必须编码到消息中的某个位置。
一种方法是将它放在用户数据域(消息主体)中。另一种技术(我们在示例代码中所使用的)是在队列名称本身中指定目的地。我们按照 "QUEUE NAME % QUEUE MANAGER NAME" 方式(中间没有空格)来使用队列名称临时保存设备队列管理器和队列名称。接收网关队列管理器只有在接收退出处理完成后才会询问队列名称,所以您有足够的时间来删除进行编码的目的地。
当 MQ 应用程序或消息代理发送消息给设备队列管理器时,队列管理器名称应该用网关队列管理器名称填充,而队列名称应该用带有分隔符 % 的目标 MQe 队列名称填充,紧接着是设备队列管理器名称。换句话说,发送应用程序的目标是网关队列管理器,而不是设备队列管理器。通过这样做,只有网关队列管理器的远程队列定义是必需的,从而将定义数降到一。
接收退出处理只是将网关队列管理器名称替换成设备队列管理器名称并整理队列名称,删除首次出现的 % 后面的字符。分隔符可以是 WebSphere MQ Application Programming Guide 第 4 章“WebSphere MQ Objects”中标题为“Rules for naming WebSphere MQ objects”的部分列出的任何有效字符。
图 4. MQ 接收退出处理
MQ 标头中为保存队列名称而分配的空间(如图 5 所示)是 48 个字符。命名 MQe 对象的最佳实践是使用尽可能最短的名称以节省发送开销,48 个字符足够将队列名称和队列管理器名称同时进行编码。在典型的设备队列管理器命名规范中,队列管理器有一个前缀,然后是一些数字字符。如果保存名称的空间非常有限,则可以将前缀作为接收退出处理的一部分添加,而让 MQ 只用数字值标识目的地。
示例代码
实现本文所描述的解决方案的 Java 代码如下所示。该逻辑处理 MQ 发送标头,其结构如图 5 所示:
图 5. MQ 传输队列标头结构
清单 1. 接收退出代码示例
package examples.mqbridge.userexit;
/* ************************************************************** */
/* Description: Extend MQ Client channel receive exit to replace */
/* the target remote queue and remote queue manager in the */
/* transmit queue header (MQXQH)with new routing information */
/* ************************************************************** */
import com.ibm.mq.*;
import com.ibm.mqe.*;
import java.io.*;
/**
*
* <p>Extends MQ Client channel receive exit to replace target remote queue and
* remote queue manager in the MQXQH header with a user specified destination.
* <p>New MQe Destination is specified in the remote queue field in the format
* <pre>
* . QueueName%QueueManagerName
* </pre>
* <p>Exceptions and error conditions returns the buffer unchanged
* <p>Trace information is logged to MQeTrace
*
**/
public class MQeMQReceiveExit implements MQReceiveExit
{
private static final int MQXQH_START = 492;
private static final int MQXQH_ID = 0x58514820;
private static final char SEPARATOR_CHAR = '/';
private static final int QUEUE_LOCN = 500;
private static final int QMGR_LOCN = QUEUE_LOCN + MQC.MQ_Q_NAME_LENGTH;
private static final int END_DEST_INFO = QUEUE_LOCN + MQC.MQ_Q_NAME_LENGTH
+ MQC.MQ_Q_MGR_NAME_LENGTH;
private static final String MODULE_NAME
= "examples.mqbridge.userexit.MQeMQReceiveExit";
//* *************************************************************************
//* Method : receiveExit
//* Parms : MQ Message buffer and channel parameters
//* Returns: MQ message buffer with new routing information
//* *************************************************************************
public byte[] receiveExit(
MQChannelExit channelExitParms,
MQChannelDefinition channelDefinition,
byte[] agentBuffer)
{
MQeTrace.trace(this, (short) 310, MQeTrace.GROUP_ENTRY,
"--> "+MODULE_NAME+", receiveExit ");
if (agentBuffer.length > MQXQH_START) {
try {
// Create an MQ message object from the received message
MQMessage mqMsg = new MQMessage();
mqMsg.write(agentBuffer,0,agentBuffer.length);
// Check that the message header has an MQXQH header
if (isHeaderMQXQH(mqMsg)) {
// The received MQXQH Header information is:
// MQXQH.remoteQMgrName = MQe Gateway Queue Manager Name
// MQXQH.remoteQName = MQe Device Queue Manager Name '/' Queue Name
//
// The code below will switch the destination as follows:
// MQXQH.remoteQMgrName = MQe Device Queue Manager Name
// MQXQH.remoteQName = MQe Device Queue Name
{
// locate the separator char
int i=QUEUE_LOCN;
while (i<END_DEST_INFO &agentBuffer[i] != SEPARATOR_CHAR ) {
i++;
}
// If a separator is found replace the destination
if ( agentBuffer[i] == SEPARATOR_CHAR ) {
agentBuffer[i++]=0x20;// clear the separator char
int j=QMGR_LOCN;// index for queue manager field
// move the queue manager to the remoteQMgrName field
while (i<END_DEST_INFO &agentBuffer[i] != 0x20 ) {
agentBuffer[j++]=agentBuffer[i];
agentBuffer[i]=0x20;
i++;
}
// clear the rest of the queue manager field
for(i=j;i<END_DEST_INFO;i++) {
agentBuffer[i]=0x20;
}
}
}
}
} catch(Exception e) {
System.out.println("Exception"+e);
MQeTrace.trace(this, (short) 315,
MQeTrace.GROUP_EXCEPTION | MQeTrace.GROUP_USER_DEFINED_1,
"Exception caught, destination left unchanged:",e);
}
}
channelExitParms.exitResponse = MQChannelExit.MQXCC_OK;
MQeTrace.trace(this, (short) 320,
MQeTrace.GROUP_EXIT | MQeTrace.GROUP_USER_DEFINED_1,
"receiveExit()");
return (agentBuffer);
}
//* *************************************************************************
//* Method : isHeaderMQXQH
//* Parms : MQ Message Buffer
//* Returns: true if a MQ Transmit header is detected
//* *************************************************************************
private boolean isHeaderMQXQH(MQMessage mqMsg)
throws IOException, MQException
{
mqMsg.seek(0);
mqMsg.skipBytes(MQXQH_START);
int hdr = mqMsg.readInt4();
return(hdr == MQXQH_ID);
}
}
|
您可以在下面下载代码。
更新网关配置
接收退出一旦编译并构建完成,就需要在网关队列管理器上对它进行部署和配置,包括在网关队列管理器上设置 MQ 客户端连接对象的接收退出属性。该属性是由实现接收退出的 Java 类的名称给出的。您可以使用 MQe Explorer 管理工具或 MQe Scripting Tool 来指定接收退出,这两个工具都可以在 WebSphere MQe Server SupportPac ES06 中获得。您也可以使用 MQe 管理命令来以编程方式指定接收退出。
图 5. 在网关处更新 MQ 通道退出
未处理的消息
如果有一个消息网关队列管理器不能够处理,则从 MQ 到网关的消息移动就会停滞下来。当网关队列管理器接收到来自 MQ 的消息时,它会询问其连接和队列定义以决定如何将消息路由到其最终目的地。通常,消息最终会被送到一个存储转发队列中以等待客户端设备接收。如果网关不知道目标队列管理器,则会执行未送达规则。此规则是作为网关侦听器的一个属性设置的。此规则的缺省行为是以越来越长的时间间隔进行重试直到达到阈值,然后最终停止该侦听器。未送达的消息将保留在 MQ 传输队列中以等待操作人员干预。您可以修改此规则来采取不同的操作,例如移除该消息并将其写入到另一个队列中。要获得示例,请参考 examples.mqbridge.rules.UndeliveredMQMessageToDLQRule。
结束语
本文演练了配置 MQ 以使用大量 MQe 队列管理器的各种选择。其中描述了每种选择的优缺点,并说明所有标准配置选项都有缺陷。然后描述一种技术,它可以将定义数从与设备队列管理器数量相同急剧降到与网关队列管理器数量相同。这种技术可以让您扩展至大量 MQe 队列管理器,而没有必须管理大量资源定义的开销。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| Sample MQ Receive Exit | MQeMQReceiveExit.zip | 2 KB |
FTP | HTTP |
|---|
参考资料
作者简介  | 
|  |
Dave Locke 是英国 IBM Hursley Software Lab 的软件工程师。他在过去的七年中从事 WebSphere MQ Everyplace 和其他普及消息传递技术的工作。 |
 | 
|  |
Lakshman Yatawara 是英国 IBM Hursley Software Lab 的高级 IT 专家和 WebSphere Software Services 专家。他在过去的六年中从事 WebSphere 消息传递产品的工作。 |
对本文的评价
|