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集群的興趣,并參加到我們的討論之中。