1. Websphere MQ 集群负载均衡的增强
在Websphere MQ集群中,成员队列管理器可以创建本地队列,并将其在集群中共享,集群中的其他成员队列管理器不需要任何额外的配置操作,就可以像访问本地队列一样向该队列放入(PUT)消息;每个成员队列管理器都可以创建与之同名的共享队列,于是该共享队列在集群中就拥有了多个副本,所有的副本将作为一个虚拟的整体;对访问者(调用MQPUT的应用)而言,它们就是一个队列,应用不需要关心如下细节:
- 物理上有多少队副本;
- 队列副本部署的物理列位置;
- 消息实际发送到哪个队列副本。
所有这些细节问题由集群负责处理,由此实现了集群的负载均衡功能。
然而,在V6之前,集群的负载均衡功能在实现上有若干的限制,其中我们最常碰到的是如下两点:
- 缺省的负载分配方式是平均主义――轮循(Round Robin),集群将同等对待所有的队列副本;要想实现更灵活的分配策略就得依靠开发人员定制自己的用户出口(User Exit)程序①。
- 负载分配的算法中有一项重要的例外――本地优先原则。一个消息进入到集群中会经由两种途径:1) 应用程序连接到集群中的某一成员队列管理器,调用MQ API将消息写入共享队列;2)集群外部的队列管理器连接到集群中的某一成员队列管理器,并将消息由通道发送过来。无论是哪种途径,消息到达的第一个队列管理器,对该消息都具有特别的意义(我喜欢将该队列管理器称之为消息的"着陆点")。本地优先原则是指如果"着陆点 "上拥有一个目标共享队列的副本,那么消息将永远不会被路由到其它远程队列管理器,只能进入"着陆点"本地的共享队列副本。也许可以为这样的实现方式想出一百条理由,但我们无法回避的是,它造成了这样一个结果:如果要从集群的外部发送消息到集群,并希望消息在集群的成员间合理的分配,那么集群中作为与外部环境接口点的队列管理器上就不能存在任何共享队列的副本,否则所有消息将积压在接口点队列管理器上,使集群的负载均衡功能英雄无用武之地。通常的解决方案是为集群配置至少一个特殊的队列管理器,通常称为网关(Gateway)队列管理器,该队列管理器上没有任何共享队列的副本,只需配置与集群外部的通讯设置(通道、监听器等)和相关的队列管理器别名(Queue-manager alias)。
即便Websphere MQ具有上述的局限,但瑕不掩瑜,在大规模集群方面,消息中间件(MOM)业内还是少有能出其右者。这也许是在消息中间件技术不算悠久的发展历程上,无法回避的成长的阵痛。毕竟,MQ还是为我们提供了解决问题的途径,虽然牺牲了些许的系统部署、管理的复杂度,手法也显的不是那么的优雅。
"是否优先使用本地副本?是否采用轮循的分配策略?",在这个问题上,使用者应当具有自由的裁量权利,并拥有便捷的设置手段。我们终于在Websphere MQ v6里找回了那些被剥夺了的选择的自由。
在Websphere MQ v6中,使用者可以根据集群成员节点的实际处理能力,合理、灵活的分配工作负载,轮循(Round Robin)顺理成章的成为缺省的分发策略。本地共享队列副本优先的策略也依然是缺省的设置,但用户可以非常方便的修改策略,使集群中的其它共享队列副本可以和本地副本一样参与到消息分发的机制当中。当然,用户出口(User Exit)程序仍然被支持,用户可以使用该机制实现更加个性化的负载均衡策略。②
为支持上述新增功能,Websphere MQ为队列、通道、队列管理器等对象增加了若干的参数。
对象 |
参数命 |
解释 |
缺省值 |
队列(Queues) |
CLWLUSEQ |
指定该队列在负载均衡策略中,本地队列副本参与分发的方式。
LOCAL:只使用本地队列。
QMGR:参考队列管理器的CLWLUSEQ定义。
ANY:将本地和远程队列同等考虑。 |
QMGR |
CLWLPRTY |
指定共享队列副本的优先级。取值范围:0~9。
集群将所有消息发送到优先级最高的队列副本, 只有当优先级高的队列副本不可用时,优先级低的队列副本才被考虑。
队列副本的优先级在通道状态检查后才被检查;所以,只有当前可用的队列管理器才可能被选择。如果通道不可用,即便相应的队列副本优先级高,也不会被选中。 |
0 |
CLWLRANK |
指定共享队列副本的等级。取值范围:0~9。
集群将所有消息发送到等级最高的队列副本。队列副本等级在通道状态检查之前被检查,所以无论对应的通道是否可用,等级高的队列都将被选择。 |
0 |
队列管理器
(Queue manager) |
CLWLUSEQ |
指定该队列管理器在负载均衡策略中,本地队列副本参与分发的方式。
LOCAL:只使用本地队列。
ANY:将本地和远程队列同等考虑。 |
LOCAL |
CLWLMRUC |
最大outbound集群通道, 取值:1~999,999,999。 |
999,999,999 |
通道(Channels) |
CLWLWGHT |
通道负载均衡权重值, 取值:1~99。
只对CLUSSDR和CLUSRCVR生效。
集群根据该权重决定分配到通道对应的队列副本上的消息数量。 |
50 |
CLWLPRTY |
指定集群通道的优先级。取值范围:0~9。
只对CLUSSDR和CLUSRCVR生效。
集群通过优先级最高的通道将所有消息发送到相应的队列副本,只有当优先级高的通道不可用时,优先级低的通道才被考虑。
通道的优先级在通道状态检查后才被检查;所以只有当前可用的通道才可能被选择。如果通道不可用,即便优先级高,也不会被选中。
队列副本优先级在通道优先级检查后被考虑。 |
0 |
CLWLRANK |
指定集群通道的等级。取值范围:0~9。
只对CLUSSDR和CLUSRCVR生效。
集群通过等级最高的通道将所有的消息发送到相应的队列副本,只有当等级高的通道不可用时,等级低的通道才被考虑。
通道等级在通道状态检查之前被检查,所以无论对应的通道是否可用,等级高的通道都将被选择。
队列副本等级在通道等级检查后被考虑。 |
0 |
2. 演示
下面,我们来看两个简单的演示场景:
在集群CL1中有三个成员队列管理器QM1、QM2、QM3 , 每个成员上都拥有一个共享队列Q1的副本。另有一个队列管理器QM4, QM4和集群CL1中的QM1之间具有收发通道。
点击查看大图
首先,依照上图配置集群的基本环境(具体的配置方法可以参考Websphere MQ产品文档《Queue Manager Clusters》和IBM开发者园地中娄丽军的文章《MQ群集的使用》)。
2.1 场景一
第一个场景中,应用程序将连接到队列管理器QM4, 发送大量消息到集群CL1中的共享队列Q1中。我们期望消息以 1:3:6的比例在QM1、QM2、QM3中分配。
首先,由于集群的接入点QM1要参与消息的分配,QM1要拥有一个共享队列Q1的副本,而根据Websphere MQ缺省分发策略(本地优先),这会造成所有消息都保存到QM1上的Q1中,因此我们必须修改变Q1的分发策略;有两种方式可以达成这个目的:
1. 修改QM1上的队列Q1的属性CLWLUSEQ为ANY,(缺省为QMGR)。
2. 修改QM1上的队列Q1的属性CLWLUSEQ为QMGR,修改队列管理器QM1的属性CLWLUSEQ为ANY。第一种方式只对Q1生效,而第二种方式则可能对队列管理器中的所有队列生效;如果集群中有大量的共享队列,并采用同样的分发策略,后一种方式会更灵活些。
runmqsc QM1
ALTER QLOCAL(Q1) CLWLUSEQ(LOCAL)
|
接下来,为了使发送来的消息可以被动态路由,需要在接入点队列管理器QM1上创建一个空的队列管理器别名(Queue-manager alias)③。
DEFINE QREMOTE(ANYONE) RNAME(' ') RQMNAME(' ')
|
队列管理器QM4置身于集群之外,在其上定义远程队列定义(假设通道、传输队列都已经就绪了):
DEFINE QREMOTE(Q1) RNAME(Q1) RQMNAME(ANYONE) XMITQ(QM1)
|
调用MQ例子程序amqsput,发送10个消息:
amqsput Q1 QM4
Sample AMQSPUT0 start
target queue is Q1
1
2
3
4
5
6
7
8
9
0
|
我们看一下这样设置的效果:
runmqsc QM1
display qlocal(Q1) CURDEPTH
AMQ8409: Display Queue details.
QUEUE(Q1) TYPE(QLOCAL)
CURDEPTH(3)
|
runmqsc QM2
display qlocal(Q1) CURDEPTH
AMQ8409: Display Queue details.
QUEUE(Q1) TYPE(QLOCAL)
CURDEPTH(3)
|
runmqsc QM3
display qlocal(Q1) CURDEPTH
AMQ8409: Display Queue details.
QUEUE(Q1) TYPE(QLOCAL)
CURDEPTH(4)
|
可以看到,各副本中的消息数分别是3、3、4。基本上被平均分配到集群中的三个成员上了。
接下来,我们设置各共享队列副本的权重;确切的讲,权重只能在通道上设置,我们要修改每个集群成员队列管理器的集群接收通道(Cluster-receiver Channel)的属性CLWLWGHT。
设置QM1的权重为1。
runmqsc QM1
ALTER CHANNEL(TO.QM1) CHLTYPE(CLUSRCVR)CLWLWGHT(1)
|
设置QM2的权重为3。
runmqsc QM2
ALTER CHANNEL(TO.QM1) CHLTYPE(CLUSRCVR)CLWLWGHT(3)
|
设置QM3的权重为6。
runmqsc QM3
ALTER CHANNEL(TO.QM1) CHLTYPE(CLUSRCVR)CLWLWGHT(6)
|
清空各队列中的消息后,再次发送消息。
amqsput Q1 QM4
Sample AMQSPUT0 start
target queue is Q1
1
2
3
4
5
6
7
8
9
0
|
看一下效果吧:
runmqsc QM1
display qlocal(Q1) CURDEPTH
AMQ8409: Display Queue details.
QUEUE(Q1) TYPE(QLOCAL)
CURDEPTH(1)
|
runmqsc QM2
display qlocal(Q1) CURDEPTH
AMQ8409: Display Queue details.
QUEUE(Q1) TYPE(QLOCAL)
CURDEPTH(3)
|
runmqsc QM3
display qlocal(Q1) CURDEPTH
AMQ8409: Display Queue details.
QUEUE(Q1) TYPE(QLOCAL)
CURDEPTH(6)
|
1、3、6 ,Bingo!
2.2 场景二
第二个场景,我们将看到所谓Hot-Standby模式:在正常状况下,消息有确定的分发目的地(一个或多个共享队列副本),当首选的目的地失效时,消息将发送到备份的(一个或多个)副本中。
这次,我们期望队列管理器QM2和QM3上的Q1副本作为正常状况下的消息目的地。在上述两个队列副本不可用时,由QM1上的Q1副本作为备份接收消息。
同样有两种设置方式:
1. 修改各成员队列管理器上的共享队列副本Q1的属性CLWLPRTY;
2. 修改各成员队列管理器上的集群接收通道的属性CLWLPRTY。
第一种方式只对Q1生效,而第二种方式则可能对相应队列管理器中的所有队列生效;如果集群中有大量的共享队列,并采用同样的分发策略,后一种方式会更灵活些。
如果两种同时设置,则集群接收通道的属性CLWLPRTY优先生效。
设置QM1上的Q1副本优先级为1。
runmqsc QM1
ALTER QLOCAL(Q1) CLWLPRTY (1)
|
设置QM2上的Q1副本优先级为2。
runmqsc QM2
ALTER QLOCAL(Q1) CLWLPRTY (2)
|
设置QM3上的Q1副本优先级为2。
runmqsc QM3
ALTER QLOCAL(Q1) CLWLPRTY (2)
|
清空各队列中的消息后,再次发送消息(这次是9个消息)。
amqsput Q1 QM4
Sample AMQSPUT0 start
target queue is Q1
1
2
3
4
5
6
7
8
9
|
我们得到如下的结果:
runmqsc QM1
display qlocal(Q1) CURDEPTH
AMQ8409: Display Queue details.
QUEUE(Q1) TYPE(QLOCAL)
CURDEPTH(0)
|
runmqsc QM2
display qlocal(Q1) CURDEPTH
AMQ8409: Display Queue details.
QUEUE(Q1) TYPE(QLOCAL)
CURDEPTH(3)
|
runmqsc QM3
display qlocal(Q1) CURDEPTH
AMQ8409: Display Queue details.
QUEUE(Q1) TYPE(QLOCAL)
CURDEPTH(6)
|
队列管理器QM2、QM3上的Q1优先级同为2,高于QM1上的Q1,所以消息在QM2、QM3中分配,分配的比例则依照之前我们设置的通道的权重3:6。
下面我们将QM3上的Q1设置为不可写入的状态,并观察一下结果。
runmqsc QM3
ALTER QLOCAL(Q1) PUT(DISABLED)
|
清空各队列中的消息后,再次发送消息。
amqsput Q1 QM4
Sample AMQSPUT0 start
target queue is Q1
1
2
3
4
5
6
7
8
9
0
|
我们得到如下的结果:
runmqsc QM1
display qlocal(Q1) CURDEPTH
AMQ8409: Display Queue details.
QUEUE(Q1) TYPE(QLOCAL)
CURDEPTH(0)
|
runmqsc QM2
display qlocal(Q1) CURDEPTH
AMQ8409: Display Queue details.
QUEUE(Q1) TYPE(QLOCAL)
CURDEPTH(10)
|
runmqsc QM3
display qlocal(Q1) CURDEPTH
AMQ8409: Display Queue details.
QUEUE(Q1) TYPE(QLOCAL)
CURDEPTH(0)
|
在QM3上的Q1不可用时,所有的消息都被发送到QM2上的Q1中。
我们再将QM2上的Q1也设置为不可写入的状态,并观察结果。
runmqsc QM2
ALTER QLOCAL(Q1) PUT(DISABLED)
|
清空各队列中的消息后,再次发送消息。
amqsput Q1 QM4
Sample AMQSPUT0 start
target queue is Q1
1
2
3
4
5
6
7
8
9
0
|
我们得到如下的结果:
runmqsc QM1
display qlocal(Q1) CURDEPTH
AMQ8409: Display Queue details.
QUEUE(Q1) TYPE(QLOCAL)
CURDEPTH(0)
|
runmqsc QM2
display qlocal(Q1) CURDEPTH
AMQ8409: Display Queue details.
QUEUE(Q1) TYPE(QLOCAL)
CURDEPTH(0)
|
runmqsc QM3
display qlocal(Q1) CURDEPTH
AMQ8409: Display Queue details.
QUEUE(Q1) TYPE(QLOCAL)
CURDEPTH(10)
|
当高优先级的Q1副本都不可用时,所有的消息都被发送到优先级较低的QM1上的Q1中。
2.3 更多
另一个我们没有深入讨论的概念是队列和通道的属性CLWLRANK(我们姑且称之为"等级"好了)。CLWLRANK和CLWLPRTY作用相似,不同之处在于CLWLRANK在通道状态检查之前生效,而CLWLPRTY在通道状态检查之后生效。简单的讲,如果设置了CLWLRANK,则消息将必定发往 CLWLRANK最高的队列副本,即使通向该目标副本的通道不可用,消息也将积压在集群的传输队列中,等待通道恢复,而不会发送到目前可用的其它 CLWLRANK较低的副本中。如果设置了CLWLPRTY,则集群会首先检查通向各副本所在地的通道状态,只有通道可用的目的地才会进入候选名单,集群将在候选名单中选择CLWLPRTY最高的副本来分发消息。
最后,我们总结一下各参数生效的先后次序:
- 通道的CLWLRANK
- 队列的CLWLRANK
- 通道状态
- 通道的CLWLPRTY
- 队列的CLWLPRTY
- 队列的CLWLWGHT
3.结束语
我们初步体会了Websphere MQ新的集群负载均衡功能概貌,当然,这还远远不足以体现它全部的内涵。如果将所有这些属性加以灵活的组合,将衍生出为数众多的负载均衡配置方案,来满足实际业务环境多样化的需求。还有相当多与负载均衡相关的内容,本文没有涉及到,但非常值得探讨,希望本文能引起诸位对MQ集群的兴趣,并参加到我们的讨论之中。