IBM WebSphere MQ (Message Queue) 是目前应用最多的消息中间件产品,它简化了应用程序之间的数据传输,提供了统一的通信标准,确保分布式网络环境下可靠的、跨平台的信息传输和数据交换。应用程序只需要将消息发送给 MQ,由 MQ 负责将消息安全可靠地发送出去。
Websphere MQ 支持集群功能。多个队列管理器可以分布在不同的机器上。集群功能简化了系统配置,不需要在每个队列管理器上创建消息通道、远程队列定义和传输队列。使用集群技术可以提高系统的可用性和实现负载均衡。
MQ 集群概述
对于 MQ 两个队列管理器之间的通信,需要创建相应的对象。在发送队列管理器上,要创建一个远程队列,这个远程队列指向远程的目的地队列。还要创建一个传输队列做为存储转发队列以及一个发送通道指向接收队列管理器。在接收队列管理器上,要创建一个本地队列和一个接收通道。如果一个网络中有多个队列管理器,并且两两之间要实现通信时,需要创建多个远程队列、传输队列和发送通道。
为了简化点对点通信的系统配置,通过 MQ 提供的群集功能,减少了集群中队列管理器上的 MQ 对象数量。群集内的两个队列管理器之间的通信,不需要两两间建立消息通道,而是使用群集通道与其它成员通信。只需要在每个队列管理器上创建两个集群通道。一个集群接收通道用来从集群中的其他队列管理器接收消息。一个集群发送通道用来发送消息。
集群的仓储库中含有集群的信息,如队列管理器的位置,通道等信息。仓储库分为完整仓储库队列管理器和部分仓储库队列管理器。完整仓储库队列管理器有集群中所有队列管理器的信息。而部分仓储库队列管理器中只有自身的和它要通信的队列管理器的信息。部分队列管理器通过与完整队列管理器通信,查询到相关的集群信息。
集群中共享的队列叫做集群队列。集群中的其他队列管理器可以向集群队列发送消息而不需要创建对应的远程队列定义。通常会在集群中创建多个同名的集群队列实例,分布在不同队列管理器上。当消息发送到集群队列时,MQ 会通过负载平衡算法,决定消息实际发送到哪里队列管理器的队列上。
创建 MQ 集群环境
创建一个简单的 MQ 集群一般需要两个完整仓储库队列管理器和两个部分仓储库队列管理器。其中两个完整仓储库队列管理器相互形成备份。本文示例中包括,两个完整仓储库队列管理器和两个部分仓储库队列管理器,以及在两个部分仓储库队列管理器上各创建一个集群队列。
用命令行创建集群
清单 1. 创建队列管理器
1
2
3
4
5
6
7
|
crtmqm -h 1024 -lp 20 -ls 5 -u FULL_QM1.DLQ FULL_QM1
strmqm FULL_QM1
runmqsc FULL_QM1
DEFINE LISTENER ('LISTENER.TCP') TRPTYPE (TCP) PORT (5000) CONTROL (QMGR)
START LISTENER ('LISTENER.TCP')
DEFINE CHANNEL ('SYSTEM.ADMIN.SVRCONN') CHLTYPE (SVRCONN)
END
|
在清单 1 中,创建了一个队列管理器 FULL_QM1。并且指定了这个队列管理器的监听端口为 5000。然后用同样的方法分别创建队列管理器 FULL_QM2,PART_QM1 和 PART_QM2,指定队列管理器的监听端口为 5001,5002 和 5003。
在创建完队列管理器后,需要将 FULL_QM1 和 FULL_QM2 添加到集群 NEW_CLUSTER 中,作为完整仓储库队列管理器。如清单 2 所示。
清单 2. 添加完整仓储库定义
1
2
3
|
echo “ALTER QMGR REPOS ('NEW_CLUSTER')” | runmqsc FULL_QM1
echo “ALTER QMGR REPOS ('NEW_CLUSTER')” | runmqsc FULL_QM2
|
在集群中,无论一个队列管理器要连接多少个远程队列管理器,最少只要创建一个集群发送通道和一个集群接收通道。
每个队列管理器都需要一个接收通道指向自己。对于队列管理器 FULL_QM1,要指定连接名 CONNAME 中的 ip 地址和端口号为 FULL_QM1 自己的 ip 地址和监听端口号。对于其他的队列管理器也一样。在清单 3 中,创建了集群中四个队列管理器的接收通道。
清单 3. 在队列管理器上创建接收通道
1
2
3
4
5
6
7
8
9
10
11
|
echo “DEFINE CHANNEL ('TO.FULL_QM1') CHLTYPE (CLUSRCVR) TRPTYPE (TCP) CONNAME
('127.0.0.1(5000)') CLUSTER ('NEW_CLUSTER')” | runmqsc FULL_QM1
echo “DEFINE CHANNEL ('TO.FULL_QM2') CHLTYPE (CLUSRCVR)
TRPTYPE (TCP) CONNAME ('127.0.0.1(5001)') CLUSTER ('NEW_CLUSTER')” | runmqsc FULL_QM2
echo “DEFINE CHANNEL ('TO.PART_QM1') CHLTYPE (CLUSRCVR)
TRPTYPE (TCP) CONNAME ('127.0.0.1(5002)') CLUSTER ('NEW_CLUSTER')” | runmqsc PART_QM1
echo “DEFINE CHANNEL ('TO.PART_QM2') CHLTYPE (CLUSRCVR)
TRPTYPE (TCP) CONNAME ('127.0.0.1(5003)') CLUSTER ('NEW_CLUSTER')” | runmqsc PART_QM2
|
每个队列管理器需要一个发送通道。在清单 4 中,定义了集群中四个队列管理器的发送通道。需要注意的是,对于两个完整仓储库队列管理器,需要建立相互间的发送通道。如对于完整仓储库队列管理器 FULL_QM1,需要建立到 FULL_QM2 的发送通道,发送通道的名字要和 FULL_QM2 的接收通道名字相同。并且要指定连接名 CONNAME 中的 ip 地址和端口号为 FULL_QM2 的 ip 地址和端口号。
而对于部分仓储库队列管理器,需要建立与某个或多个完整仓储库队列管理器的发送通道。对于 PART_QM1,这里建立了到完整仓储库队列管理器 FULL_QM1 的发送通道,并且发送通道的名字要和 FULL_QM1 的接收通道名字相同。连接名 CONNAME 也要指定为 FULL_QM1 的 ip 地址和端口号。另外,不需要定义 PART_QM1 指向另一个部分仓储库队列管理器 PART_QM2 的发送通道。
清单 4. 在队列管理器上创建发送通道
1
2
3
4
5
6
7
8
9
10
11
|
echo “DEFINE CHANNEL ('TO.FULL_QM2') CHLTYPE (CLUSSDR) TRPTYPE
(TCP) CONNAME ('127.0.0.1(5001)') CLUSTER ('NEW_CLUSTER')” | runmqsc FULL_QM1
echo “DEFINE CHANNEL ('TO.FULL_QM1') CHLTYPE (CLUSSDR)
TRPTYPE (TCP) CONNAME ('127.0.0.1(5000)') CLUSTER ('NEW_CLUSTER') ” | runmqsc FULL_QM2
echo “DEFINE CHANNEL ('TO.FULL_QM1') CHLTYPE (CLUSSDR)
TRPTYPE (TCP) CONNAME ('127.0.0.1(5000)') CLUSTER ('NEW_CLUSTER') ” | runmqsc PART_QM1
echo “DEFINE CHANNEL ('TO.FULL_QM2') CHLTYPE (CLUSSDR)
TRPTYPE (TCP) CONNAME ('127.0.0.1(5001)') CLUSTER ('NEW_CLUSTER') ” | runmqsc PART_QM2
|
集群的负载均衡是通过在集群内的不同队列管理器上建立同名的队列,即创建同一个队列的多个实例来实现的。每个队列实例都可以作为消息的目的地,MQ 使用负载平衡算法决定消息实际发送到哪个队列管理器。清单 5 中,在两个部分仓储库队列管理器上分别定义了一个集群队列 TEST_QUEUE,并且指定其 Cluster 属性值为 NEW_CLUSTER,这样这两个同名队列就可以在集群中共享了。
清单 5. 在两个部分仓储库队列管理器上定义集群队列
1
2
3
|
echo “DEFINE QLOCAL ('TEST_QUEUE') CLUSTER ('NEW_CLUSTER')” | runmqsc PART_QM1
echo “DEFINE QLOCAL ('TEST_QUEUE') CLUSTER ('NEW_CLUSTER')” | runmqsc PART_QM2
|
至此,已创建完一个简单的集群。可以通过 DISPLAY CLUSQMGR 命令来显示集群中的队列管理器的相关信息。清单 6 中列出了部分的信息。连接到完整仓储库队列管理器 FULL_QM1,在结果中共显示了四条通道。其中 TO.FULL_QM1 类型为 CLUSRCVR,指向 FULL_QM1,是接收通道。TO.FULL_QM2 为发送通道,指向 FULL_QM2,类型 CLUSSDRB 说明是显示创建的。TO.PART_QM1 和 TO.PART_QM2 是发送通道,分别指向 PART_QM1 和 PART_QM2,类型都为 CLUSSDRA,是集群自动生成的。
清单 6. 集群中队列管理器的信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
echo “DISPLAY CLUSQMGR(*) CLUSTER('NEW_CLUSTER') ALL” | runmqsc FULL_QM1
CLUSQMGR(FULL_QM1)
CHANNEL(TO.FULL_QM1)
CLUSTER(NEW_CLUSTER)
CONNAME(127.0.0.1(5000))
DEFTYPE(CLUSRCVR)
CLUSQMGR(FULL_QM2)
CHANNEL(TO.FULL_QM2)
CLUSTER(NEW_CLUSTER)
CONNAME(127.0.0.1(5001))
DEFTYPE(CLUSSDRB)
CLUSQMGR(PART_QM1)
CHANNEL(TO.PART_QM1)
CLUSTER(NEW_CLUSTER)
CONNAME(127.0.0.1(5002))
DEFTYPE(CLUSSDRA)
CLUSQMGR(PART_QM2)
CHANNEL(TO.PART_QM2)
CLUSTER(NEW_CLUSTER)
CONNAME(127.0.0.1(5003))
DEFTYPE(CLUSSDRA)
|
通过 DISPLAY QUEUE 命令显示集群中队列的相关信息,如清单 7 所示。连接到完整仓储库队列管理器 FULL_QM1 上,在结果中共显示了两个集群队列实例 TEST_QUEUE。
清单 7. 集群队列的信息
1
2
3
4
|
echo “DISPLAY QUEUE(*) CLUSINFO” | runmqsc FULL_QM1
QUEUE(TEST_QUEUE) TYPE(QCLUSTER)
QUEUE(TEST_QUEUE) TYPE(QCLUSTER)
|
通过这些命令,可以了解集群的具体结构。图 1 是当前显示创建的集群 NEW_CLUSTER 的拓扑图。对于 PART_QM1,我们只是显示创建了 PART_QM1 到 FULL_QM1 的发送通道,之后集群会自动创建 PART_QM1 到 FULL_QM2 的发送通道。同样集群也会自动创建 PART_QM2 到 FULL_QM1 的发送通道。最后形成的集群拓扑图如图 2 所示。
图 1. 显示创建的集群 NEW_CLUSTER 的拓扑图
图 2. 集群 NEW_CLUSTER 的拓扑图
用 MQ explorer 创建集群
与用命令行创建集群相比,用 MQ explorer 更直观,用来创建一个简单的集群比较方便。首先,在 MQ explorer 中选择队列管理器 -> 右键新建 -> 队列管理器,创建四个队列管理器,分别为 FULL_QM1,FULL_QM2,PART_QM1 和 PART_QM2。然后选择队列管理器集群 -> 右键新建 -> 队列管理器集群,创建集群 NEW_CLUSTER。如图 3 所示。
图 3. 创建集群
然后添加完整存储库队列管理器到集群。在添加到集群队列管理器选项中分别选择 FULL_QM1 和 FULL_QM2。如图 4 和图 5 所示。
图 4. 选择第一个完整存储库队列管理器
图 5. 选择第二个完整存储库队列管理器
在第一个完整存储库队列管理器 FULL_QM1 上创建接收方通道,连接通道的端口号要设为 FULL_QM1 的监听端口 5000。用同样的方法在第二个完整存储库队列管理器 FULL_QM2 上创建接收方通道,如图 6 和图 7 所示。
图 6. 在第一个完整存储库队列管理器上创建接收通道
图 7. 在第二个完整存储库队列管理器上创建接收通道
至此,已经创建了集群 NEW_CLUSTER。现在需要将两个部分存储库队列管理器 PART_QM1 和 PART_QM2 添加到集群。在 MQ explorer 中,选择新创建的集群 NEW_CLUSTER-> 右键将队列集群管理器添加到集群,首先添加 PART_QM1 到集群,并且选择为部分仓储库队列管理器,如图 8 和图 9 所示。
图 8. 添加队列管理器到集群
图 9. 选择为部分存储库
定义 PART_QM1 队列管理器接收方通道时,需要将端口号指定为该队列管理器自身监听的端口号。如图 10 所示。
图 10. 定义队列管理器的集群接收方通道
由于部分仓储库队列管理器需要将信息同步到完整仓储库队列管理器,所以需要指定信息要发送到的完整仓储库队列管理器,可以选择 FULL_QM1 或 FULL_QM2 或都选择。这里选择 FULL_QM1,如图 11 所示。然后用同样的方法,将 PART_QM2 添加到集群。
图 11. 选择将信息发送至的完整仓储库队列管理器
最后需要在两个部分仓储库队列管理器上定义各自的集群队列 TEST_QUEUE。如图 12 所示,这里将缺省绑定类型设为不固定。
图 12.在两个部分仓储库队列管理器上定义集群队列
创建完两个集群队列后,可以在两个完整仓储库队列管理器 FULL_QM1 和 FULL_QM2 上看到两个集群队列 TEST_QUEUE。如图 13 所示。
图 13. 创建成功的两个集群队列
在之前显示建立的集群拓扑图中提到,本文只显示建立 PART_QM1 到完整仓储库队列管理器 FULL_QM1 的发送通道。而 PART_QM1 到 FULL_QM2 的发送通道是集群自动建立的。因此,在 PART_QM1 的集群发送通道中可以看到两个发送通道,如图 14 所示,其中 TO.FULL_QM1 发送通道是通过 MQ explorer 显式创建的,定义类型为自动显示集群发送方。而 TO.FULL_QM2 发送通道是系统自动创建的,定义类型为自动集群发送方。
图 14. 队列管理器 PART_QM1 的集群发送通道
使用示例应用程序演示 MQ 集群的负载平衡效果
创建完一个简单的集群后,通过配置可以实现群集的负载均衡效果。本文在集群中定义了集群队列 TEST_QUEUE 的两个同名队列实例,每个队列实例在不同的队列管理器上。当应用程序把消息发送到该集群队列时,MQ 使用负载平衡算法决定消息实际发送哪个队列管理器。
示例程序首先连接到一个完整仓储库队列管理器 FULL_QM1,将绑定的类型设定为 MQOO_BIND_AS_Q_DEF,然后将五个消息放入集群队列 TEST_QUEUE 中,如清单 8 所示。绑定类型设定为 MQOO_BIND_AS_Q_DEF 是把连接到队列的绑定类型指定为队列缺省的绑定类型,本例中为不固定,是在创建集群队列时,通过设置缺省绑定类型指定的,如图 12 所示。
清单 8. 将消息放入集群队列的示例程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
public class ClusterPut {
private static final String HOSTNAME = "127.0.0.1";
private static final int PORT = 5000;
private static final String CHANNEL = "SYSTEM.DEF.SVRCONN";
private static final int CCSID = 1208;
//we can also specify FULL_QM2 as queue manager
private static final String QM_NAME = "FULL_QM1";
private static final String Q_NAME = "TEST_QUEUE";
public static void main(String[] args) throws Exception {
// Set up WebSphere MQ environment
MQEnvironment.hostname = HOSTNAME;
MQEnvironment.port = PORT;
MQEnvironment.channel = CHANNEL;
MQEnvironment.CCSID = CCSID;
// Create a connection to the QueueManager
MQQueueManager qMgr = new MQQueueManager(QM_NAME);
// Specify the queue that we wish to open and the open options.
//MQOO_BIND_AS_Q_DEF option is specified here, so bind type is determined by
//default queue bind type
// We can also specify MQOO_BIND_ON_OPEN or MQOO_BIND_NOT_FIXED to cover
//default queue bind type
MQQueue queue = qMgr.accessQueue(Q_NAME, MQConstants.MQOO_BIND_AS_Q_DEF
+ MQConstants.MQOO_OUTPUT);
// Define a simple WebSphere MQ Message and write some text in UTF8 format
MQMessage msg = new MQMessage();
msg.writeUTF("Hello World!");
// Put five messages to the cluster queue with default put message options
queue.put(msg, new MQPutMessageOptions());
queue.put(msg, new MQPutMessageOptions());
queue.put(msg, new MQPutMessageOptions());
queue.put(msg, new MQPutMessageOptions());
queue.put(msg, new MQPutMessageOptions());
// Close the queue and disconnect from the QueueManager
queue.close();
qMgr.disconnect();
}
}
|
指定集群队列的缺省绑定类型为不固定,程序第一次运行后,五条消息分别放入部分仓储库队列管理器 PART_QM1 和 PART_QM2 的 TEST_QUEUE 队列中。如果不指定队列等级和优先级,程序运行后,在 PART_QM1 的 TEST_QUEUE 队列中有三条消息,在 PART_QM2 的 TEST_QUEUE 队列中有两条消息。
当集群队列的缺省绑定类型为打开时,如图 15 所示,程序第一次运行后,五条消息都放入 PART_QM1 的 TEST_QUEUE 队列中。程序第二次运行后,五条消息都放入 PART_QM2 的 TEST_QUEUE 队列中。指定为打开时,相当于在程序打开 TEST_QUEUE 集群队列时,就决定要使用哪一个部分仓储库队列管理器上的队列,之后,在程序断开与队列管理器的连接前,所有的消息都放到那个队列里。
图 15. 队列的缺省绑定类型
如果挂起 PART_QM1,则无论绑定类型指定为哪个选项,所有的消息都将发送到 PART_QM2 上的集群队列里。
accessQueue 函数还有另外两个选项 MQOO_BIND_NOT_FIXED 和 MQOO_BIND_ON_OPEN,缺省值为 MQOO_BIND_ON_OPEN。如果指定为 MQOO_BIND_ON_OPEN,消息只发送给某一队列实例,相当于之前把集群队列的缺省绑定类型设定为打开时。在程序中指定绑定的类型为 MQOO_BIND_NOT_FIXED 或者 MQOO_BIND_ON_OPEN 会覆盖集群队列的缺省绑定类型。
使用集群队列时需要注意的几点
如果对消息的逻辑处理有要求,或者对消息的处理顺序有要求,这时需要使用 MQOO_BIND_ON_OPEN 选项。例如,程序 A 向某个集群队列发送两条相互关联的消息,一条消息包含元数据,另一条消息包含实际数据内容,它们之间通过 groupId 相互联系。并且该集群队列在集群的两个队列管理器上各有一个同名集群队列实例。现在程序 B 需要从集群队列中取出相互关联的两条消息进行处理,如果这两条消息分别被发送到了两个不同的集群队列实例中,则程序 B 打开一个集群队列实例后,无法通过其中一条消息的 groupId,在同一个集群队列实例中找到另一条消息。要解决这个问题,可以将相互关联的消息发送到同一个集群队列实例上。在程序 A 将消息放入集群队列时,需要指定 MQOO_BIND_ON_OPEN 选项。
集群只在消息进入时实现负载平衡,一旦消息进入某个队列管理器上的集群队列,它就只能由该队列管理器处理。如果在消息被处理完之前,该队列管理器被挂起或者发生故障,已经进入该集群队列的消息将不能被处理。即其他队列管理器无法处理该队列管理器上同名的集群队列实例。这一点和 z/OS 的共享队列不同。在 z/OS 平台上,如果使用共享队列,多个队列管理器使用的是同一个队列实例,即使某个队列管理器不工作,其他的队列管理器可以处理该共享队列上的消息。
结束语
本文主要介绍了 Websphere MQ 集群的基本概念。通过命令和 MQ explorer 演示如何创建一个简单的 MQ 集群,并通过把消息放入集群队列的具体实例,实现负载平衡的效果。最后,对如何使用集群队列提出了一些建议。通过本文,读者可以对 MQ 集群有一个深入的了解。