通過之前的文章,我們討論了ActiveMQ的基本使用,包括單個ActiveMQ服務節點的性能特徵,關鍵調整參數;我們還介紹了單個ActiveMQ節點上三種不同的持久化存儲方案,並討論了這三種不同的持久化存儲方案的配置和性能特點。但是這還遠遠不夠,因爲在生產環境中爲了保證讓我們設計的消息服務方案能夠持續工作,我們還需要爲消息中間件服務搭建集羣環境,從而在保證消息中間件服務可靠性和處理性能。
2、ActiveMQ多節點方案
集羣方案主要爲了解決系統架構中的兩個關鍵問題:高可用和高性能。ActiveMQ服務的高可用性是指,在ActiveMQ服務性能不變、數據不丟失的前提下,確保當系統災難出現時ActiveMQ能夠持續提供消息服務,高可靠性方案最終目的是減少整個ActiveMQ停止服務的時間。
ActiveMQ服務的高性能是指,在保證ActiveMQ服務持續穩定性、數據不丟失的前提下,確保ActiveMQ集羣能夠在單位時間內吞吐更高數量的消息、確保ActiveMQ集羣處理單條消息的時間更短、確保ActiveMQ集羣能夠容納更多的客戶端穩定連接。
下面我們分別介紹如何通過多個ActiveMQ服務節點集羣方式,分別提供熱備方案和高性能方案。最後我們討論如何將兩種方案結合在一起,最終形成在生成環境下使用的推薦方案。
2-2、ActiveMQ高性能方案
ActiveMQ的多節點集羣方案,主要有動態集羣和靜態集羣兩種方案。所謂動態集羣就是指,同時提供消息服務的ActiveMQ節點數量、位置(IP和端口)是不確定的,當某一個節點啓動後,會通過網絡組播的方式向其他節點發送通知(同時接受其他節點的組播信息)。當網絡中其他節點收到組播通知後,就會向這個節點發起連接,最終將新的節點加入ActiveMQ集羣;所謂靜態集羣是指同時提供消息服務的多個節點的位置(IP和端口)是確定的,每個節點不需要通過廣播的方式發現目標節點,只需要在啓動時按照給定的位置進行連接。
靜態集羣方案
動態集羣方案
2-1-1、基於組播(multicast)的節點發現
在使用動態集羣配置時,當某個ActiveMQ服務節點啓動後並不知道整個網絡中還存在哪些其他的服務節點。所以ActiveMQ集羣需要規定一種節點與節點間的發現機制,以保證能夠解決上述問題。ActiveMQ集羣中,使用“組播”原理進行其他節點的發現。
組播(multicast)基於UDP協議,它是指在一個可連通的網絡中,某一個數據報發送源向一組數據報接收目標進行操作的過程。在這個過程中,數據報發送者只需要向這個組播地址(一個D類IP)發送一個數據報,那麼加入這個組播地址的所有接收者都可以收到這個數據報。組播實現了網絡中單點到多點的高效數據傳送,能夠節約大量網絡帶寬,降低網絡負載。
在IP協議中,規定的D類IP地址爲組播地址。224.0.0.0~239.255.255.255這個範圍內的IP都是D類IP地址,其中有一些IP段是保留的有特殊含義的:
224.0.0.0~224.0.0.255:這個D類IP地址段爲保留地址,不建議您在開發過程中使用,因爲可能產生衝突。例如224.0.0.5這個組播地址專供OSPF協議(是一種路由策略協議,用於找到最優路徑)使用的組播地址;224.0.0.18這個組播地址專供VRRP協議使用(VRRP協議是虛擬路由器冗餘協議)。
224.0.1.0~224.0.1.255:這個D類IP地址爲公用組播地址,用於在整個Internet網絡上進行組播。除非您有頂級DNS的控制/改寫權限,否則不建議在局域網內使用這個組播地址斷。
239.0.0.0~239.255.255.255:這個D類IP地址段爲推薦在局域網內使用的組播地址段。注意,如果要在局域網內使用組播功能,需要局域網中的交換機/路由器支持組播功能。幸運的是,目前市面上只要不是太過低端的交換機/路由器,都支持組播功能(組播功能所使用的主要協議爲IGMP協議,關於IGMP協議的細節就不再進行深入了)。
下面我們使用java語言,編寫一個局域網內的組播發送和接受過程。以便讓各位讀者對基於組播的節點發現操作有一個直觀的理解。雖然ActiveMQ中關於節點發現的過程,要比以下的示例複雜得多,但是基本原理是不會改變的。
- 組播數據報發送者:
package multicast;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.util.Date;
public class SendMulticast {
public static void main(String[] args) throws Throwable {
// 組播地址
InetAddress group = InetAddress.getByName("239.0.0.5");
// 組播端口,同時也是UDP 數據報的發送端口
int port = 19999;
MulticastSocket mss = null;
// 創建一個用於發送/接收的MulticastSocket組播套接字對象
mss = new MulticastSocket(port);
// 創建要發送的組播信息和UDP數據報
// 攜帶的數據內容,就是這個activeMQ服務節點用來提供Network Connectors的TCP/IP地址和端口等信息
String message = "我是一個活動的activeMQ服務節點(節點編號:yyyyyyy),我的可用tcp信息爲:XXXXXXXXXX : ";
byte[] buffer2 = message.getBytes();
DatagramPacket dp = new DatagramPacket(buffer2, buffer2.length, group, port);
// 使用組播套接字joinGroup(),將其加入到一個組播
mss.joinGroup(group);
// 開始按照一定的週期向加入到224.0.0.5組播地址的其他ActiveMQ服務節點進行廣播
Thread thread = Thread.currentThread();
while (!thread.isInterrupted()) {
// 使用組播套接字的send()方法,將組播數據包對象放入其中,發送組播數據包
mss.send(dp);
System.out.println(new Date() + "發起組播:" + message);
synchronized (SendMulticast.class) {
SendMulticast.class.wait(5000);
}
}
mss.close();
}
}
- 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
- 組播數據報接收者:
package multicast;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
/**
* 測試接收組播信息
* @author yinwenjie
*/
public class AcceptMulticast {
public static void main(String[] args) throws Throwable {
// 建立組播套接字,並加入分組
MulticastSocket multicastSocket = new MulticastSocket(19999);
// 注意,組播地址和端口必須和發送者的一直,才能加入正確的組
InetAddress ad = InetAddress.getByName("239.0.0.5");
multicastSocket.joinGroup(ad);
// 準備接收可能的組播信號
byte[] datas = new byte[2048];
DatagramPacket data = new DatagramPacket(datas, 2048 ,ad , 19999);
Thread thread = Thread.currentThread();
// 開始接收組播信息,並打印出來
System.out.println(".....開始接收組播信息.....");
while(!thread.isInterrupted()) {
multicastSocket.receive(data);
int leng = data.getLength();
System.out.println(new String(data.getData() , 0 , leng , "UTF-8"));
}
multicastSocket.close();
}
}
- 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
另外,我們之前講過的DUBBO框架中,也有基於“組播”的發現/註冊管理。具體可參考DUBBO框架中的com.alibaba.dubbo.registry.multicast.MulticastRegistry類和其引用類(以下爲MulticastRegistry類中,創建組播套接字和接受組播數據報的關鍵代碼段):
......
mutilcastAddress = InetAddress.getByName(url.getHost());
mutilcastPort = url.getPort() <= 0 ? DEFAULT_MULTICAST_PORT : url.getPort();
mutilcastSocket = new MulticastSocket(mutilcastPort);
mutilcastSocket.setLoopbackMode(false);
mutilcastSocket.joinGroup(mutilcastAddress);
Thread thread = new Thread(new Runnable() {
public void run() {
byte[] buf = new byte[2048];
DatagramPacket recv = new DatagramPacket(buf, buf.length);
while (! mutilcastSocket.isClosed()) {
try {
mutilcastSocket.receive(recv);
String msg = new String(recv.getData()).trim();
int i = msg.indexOf('\n');
if (i > 0) {
msg = msg.substring(0, i).trim();
}
MulticastRegistry.this.receive(msg, (InetSocketAddress) recv.getSocketAddress());
Arrays.fill(buf, (byte)0);
} catch (Throwable e) {
if (! mutilcastSocket.isClosed()) {
logger.error(e.getMessage(), e);
}
}
}
}
}, "DubboMulticastRegistryReceiver");
thread.setDaemon(true);
thread.start();
......
- 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
2-1-2、橋接Network Bridges
爲了實現ActiveMQ集羣的橫向擴展要求和高穩定性要求,ActiveMQ集羣提供了Network Bridges功能。通過Network Bridges功能,技術人員可以將多個ActiveMQ服務節點連接起來。並讓它們通過配置好的策略作爲一個整體對外提供服務。
這樣的服務策略主要包括兩種:主/從模式和負載均衡模式。對於第一種策略我們會在後文進行討論。本節我們要重點討論的是基於Network Bridges的負載均衡模式。
2-1-3、動態Network Connectors
既然已經講述了ActiveMQ中的動態節點發現原理和ActiveMQ Network Bridges的概念,那麼關於ActiveMQ怎樣配置集羣的方式就是非常簡單的問題了。我們先來討論如何進行基於組播發現的ActiveMQ負載均衡模式的配置——動態網絡連接Network Connectors;再來討論基於固定地址的負載均衡模式配置——靜態網絡連接Network Connectors。
要配置基於組播發現的ActiveMQ負載均衡模式,其過程非常簡單。開發人員只需要在每一個ActiveMQ服務節點的主配置文件中(activemq.xml),添加/更改 以下配置信息即可:
......
<transportConnectors>
<!-- 在transportConnector中增加discoveryUri屬性,表示這個transportConnector是要通過組播告知其它節點的:使用這個transportConnector位置連接我 -->
<transportConnector name="auto" uri="auto+nio://0.0.0.0:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600&org.apache.activemq.transport.nio.SelectorManager.corePoolSize=20&org.apache.activemq.transport.nio.SelectorManager.maximumPoolSize=50&consumer.prefetchSize=5" discoveryUri="multicast://239.0.0.5" />
</transportConnectors>
......
<!-- 關鍵的networkConnector標籤, uri屬性標示爲組播發現-->
<networkConnectors>
<networkConnector uri="multicast://239.0.0.5" duplex="false"/>
</networkConnectors>
......
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
2-1-3-1:networkConnector標籤
如果使用ActiveMQ的組播發現功能,請在networkConnector標籤的uri屬性中添加如下格式的信息:
multicast://[組播地址][:端口]
- 1
例如,您可以按照如下方式使用ActiveMQ默認的組播地址來發現網絡種其他ActiveMQ服務節點:
#ActiveMQ集羣默認的組播地址(239.255.2.3):
multicast://default
- 1
- 2
也可以按照如下方式,指定一個組播地址——這在高安全級別的網絡中很有用,因爲可能其他的組播地址已經被管理員禁用。注意組播地址只能是D類IP地址段:
#使用組播地址239.0.0.5
multicast://239.0.0.5
- 1
- 2
以下是通過抓包軟件獲得的的組播UDP報文:
從上圖中我們可以獲得幾個關鍵信息:
192.168.61.138和192.168.61.139這兩個IP地址分別按照一定的週期(1秒一次),向組播地址239.0.0.5發送UDP數據報。以便讓在這個組播地址的其它服務節點能夠感知自己的存在
另外,以上UDP數據報文使用的端口是6155。您也可以更改這個端口信息通過類似如下的方式:
#使用組播地址239.0.0.5:19999
multicast://239.0.0.5:19999
- 1
- 2
- 每個UDP數據報中,包含的主要信息包括本節點ActiveMQ的版本信息,以及連接到自己所需要使用的host名字、協議名和端口信息。類似如下:
default.ActiveMQ-4.ailve%localhost%auto+nio://activemq:61616
- 1
2-1-3-2:transportConnector標籤的關聯設置
任何一個ActiveMQ服務節點A,要連接到另外的ActiveMQ服務節點,都需要使用當前節點A已經公佈的transportConnector連接端口,例如以下配置中,能夠供其它服務節點進行連接的就只有兩個transportConnector連接中的任意一個:
......
<transportConnectors>
<!-- 其它ActiveMQ服務節點,只能使用以下三個連接協議和端口進行連接 -->
<!-- DOS protection, limit concurrent connections to 1000 and frame size to 100MB -->
<transportConnector name="tcp" uri="tcp://0.0.0.0:61614?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
<transportConnector name="nio" uri="nio://0.0.0.0:61618?maximumConnections=1000" />
<transportConnector name="auto" uri="auto://0.0.0.0:61617?maximumConnections=1000" />
</transportConnectors>
......
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
那麼要將哪一個連接方式通過UDP數據報向其他ActiveMQ節點進行公佈,就需要在transportConnector標籤上使用discoveryUri屬性進行標識,如下所示:
......
<transportConnectors>
......
<transportConnector name="ws" uri="ws://0.0.0.0:61614?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
<transportConnector name="auto" uri="auto+nio://0.0.0.0:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600" discoveryUri="multicast://239.0.0.5" />
</transportConnectors>
......
<networkConnectors>
<networkConnector uri="multicast://239.0.0.5"/>
</networkConnectors>
......
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
2-1-3-3:其他注意事項
關於防火牆:請記得關閉您Linux服務器上對需要公佈的IP和端口的限制;
關於hosts路由信息:由於基於組播的動態發現機制,能夠找到的是目標ActiveMQ服務節點的機器名,而不是直接找到的IP。所以請設置當前服務節點的hosts文件,以便當前ActiveMQ節點能夠通過hosts文件中的IP路由關係,得到機器名與IP的映射:
# hosts文件
......
192.168.61.139 activemq1
192.168.61.138 activemq2
......
- 1
- 2
- 3
- 4
- 5
- 6
- 7
關於哪些協議能夠被用於進行Network Bridges連接:根據筆者以往的使用經驗,只有tcp頭的uri格式(openwire協議)能夠被用於Network Bridges連接;當然您可以使用auto頭,因爲其兼容openwire協議;另外,您還可以指定爲附加nio頭。
2-1-4、靜態Network Connectors
相比於基於組播發現方式的動態Network Connectors而言,雖然靜態Network Connectors沒有那樣靈活的橫向擴展性,但是卻可以適用於網絡環境受嚴格管理的情況。例如:管理員關閉了交換機/路由器的組播功能、端口受到嚴格管控等等。
配置靜態Network Connectors的ActiveMQ集羣的方式也很簡單,只需要更改networkConnectors標籤中的配置即可,而無需關聯改動transportConnectors標籤。但是配置靜態Network Connectors的ActiveMQ集羣時,需要注意非常關鍵的細節:每一個節點都要配置其他所有節點的連接位置。
爲了演示配置過程,我們假設ActiveMQ集羣由兩個節點構成,分別是activemq1:192.168.61.138 和 activemq2:192.168.61.139。那麼配置情況如下所示:
- 192.168.61.138:需要配置activemq2的位置信息以便進行連接:
......
<transportConnectors>
<transportConnector name="auto" uri="auto+nio://0.0.0.0:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600&consumer.prefetchSize=5"/>
</transportConnectors>
......
<!-- 請注意,一定需要192.168.61.139(activemq2)提供了這樣的連接協議和端口 -->
<networkConnectors>
<networkConnector uri="static:(auto+nio://192.168.61.139:61616)"/>
</networkConnectors>
......
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 192.168.61.139:需要配置activemq1的位置信息以便進行連接:
......
<transportConnectors>
<transportConnector name="auto" uri="auto+nio://0.0.0.0:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600&consumer.prefetchSize=5"/>
</transportConnectors>
......
<!-- 請注意,一定需要192.168.61.138(activemq1)提供了這樣的連接協議和端口 -->
<networkConnectors>
<networkConnector uri="static:(auto+nio://192.168.61.138:61616)"/>
</networkConnectors>
......
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
同理,如果您的ActiveMQ集羣規劃中有三個ActiveMQ服務節點,那麼任何一個節點都應該配置其它兩個服務節點的連接方式。在配置格式中使用“,”符號進行分割:
......
<networkConnectors>
<networkConnector uri="static:(tcp://host1:61616,tcp://host2:61616,tcp://..)"/>
</networkConnectors>
......
- 1
- 2
- 3
- 4
- 5
以下是配置完成後可能的效果:
- 192.168.61.138(activemq1):
- 192.168.61.139(activemq2):
2-1-5、其他配置屬性
下表列舉了在networkConnector標籤中還可以使用的屬性以及其意義。請特別注意其中的duplex屬性。如果只從字面意義理解該屬性,則被稱爲“雙工模式”;如果該屬性爲true,當這個節點使用Network Bridge連接到其它目標節點後,將強制目標也建立Network Bridge進行反向連接。其目的在於讓消息既能發送到目標節點,又可以通過目標節點接受消息,但實際上大多數情況下是沒有必要的,因爲目標節點一般都會自行建立連接到本節點。所以,該duplex屬性的默認值爲false。
屬性名稱 | 默認值 | 屬性意義 |
---|---|---|
name | bridge | 名稱 |
dynamicOnly | false | 如果爲true, 持久訂閱被激活時才創建對應的網路持久訂閱。 |
decreaseNetworkConsumerPriority | false | 如果爲true,網絡的消費者優先級降低爲-5。如果爲false,則默認跟本地消費者一樣爲0. |
excludedDestinations | empty | 不通過網絡轉發的destination |
dynamicallyIncludedDestinations | empty | 通過網絡轉發的destinations,注意空列表代表所有的都轉發。 |
staticallyIncludedDestinations | empty | 匹配的都將通過網絡轉發-即使沒有對應的消費者,如果爲默認的“empty”,那麼說明所有都要被轉發 |
duplex | false | 已經進行詳細介紹的“雙工”屬性。 |
prefetchSize | 1000 | 設置網絡消費者的prefetch size參數。如果設置成0,那麼就像之前文章介紹過的那樣:消費者會自己輪詢消息。顯然這是不被允許的。 |
suppressDuplicateQueueSubscriptions | false | 如果爲true, 重複的訂閱關係一產生即被阻止(V5.3+ 的版本中可以使用)。 |
bridgeTempDestinations | true | 是否廣播advisory messages來創建臨時destination。 |
alwaysSyncSend | false | 如果爲true,非持久化消息也將使用request/reply方式代替oneway方式發送到遠程broker(V5.6+ 的版本中可以使用)。 |
staticBridge | false | 如果爲true,只有staticallyIncludedDestinations中配置的destination可以被處理(V5.6+ 的版本中可以使用)。 |
以下這些屬性,只能在靜態Network Connectors模式下使用
屬性名稱 | 默認值 | 屬性意義 |
---|---|---|
initialReconnectDelay | 1000 | 重連之前等待的時間(ms) (如果useExponentialBackOff爲false) |
useExponentialBackOff | true | 如果該屬性爲true,那麼在每次重連失敗到下次重連之前,都會增大等待時間 |
maxReconnectDelay | 30000 | 重連之前等待的最大時間(ms) |
backOffMultiplier | 2 | 增大等待時間的係數 |
請注意這些屬性,並不是networkConnector標籤的屬性,而是在uri屬性中進行設置的,例如:
uri="static:(tcp://host1:61616,tcp://host2:61616)?maxReconnectDelay=5000&useExponentialBackOff=false"
- 1
============
(接下文)