Networks of brokers
一、說明
是指多個broker組成集羣,當其中一個broker的消費者出問題導致消息堆積無法消費掉時,通過ActiveMQ支持的Network of Broker方案可將該broker堆積的消息轉發到其他有消費者的broker
ActiveMQ集羣有好幾種模式,其中網絡集羣模式分爲靜態網絡和動態網絡;本文主要介紹ActiveMQ的靜態網絡集羣模式;
1、靜態網絡:就是在Broker集羣配置中配置static的網絡地址,以供服務啓動的時候建立網絡集羣
靜態網絡的實現,主要是基於networkConnector元素配置完成
2、動態網絡:是通過配置發現地址,在服務啓動後還可以動態的增加節點,比較適用於動態擴容需求的場景;主要是基於transportConnector元素配置完成
二、集羣搭建
ActiveMQ的networkConnector默認是單向的,一個Broker在一端發送消息,另一個Broker在另一端接收消息,這就是所謂的"橋接"。ActiveMQ也支持雙向鏈接,創建一個雙向的通道對於兩個Broker不僅發送消息而且也能從相同的通道接收消息,通常作爲duplex connector來映射
一般推薦的靜態網絡應用架構如下:
以下根據筆者環境進行一個靜態網絡集羣的搭建;
(應爲硬件資源限制,筆者直接在單臺機器搭建一個集羣)
1. 目錄規劃
/opt/server
broker1、broker2、broker3
2. 服務器及端口規劃:
序號 |
服務名稱 |
服務器IP |
服務端口 |
監控端口 |
1 |
broker1 |
10.10.1.44 |
61616 5762 1883 |
8161 |
2 |
broker2 |
10.10.1.44 |
61617 5763 1884 |
8162 |
3 |
broker3 |
10.10.1.44 |
61618 5764 1885 |
8163 |
爲了便於記憶,我在本地做了host映射,server1.com ,後在訪問服務器或是控制檯可以直接用http://server1.com:8161訪問
3. 服務器參數配置
以上三個節點需要在每個節點的 conf/activemq.xml中添加靜態網絡配置的參數,如下
<networkConnectors>
<networkConnector name="bridge" uri="static://(tcp://localhost:61616,tcp://localhost:61617,tcp://localhost:61618)" dynamicOnly="false" duplex="true" />
</networkConnectors>
1.name: 默認的bridge 2.dynamicOnly: 默認是false,如果爲true,持久訂閱被激活時才創建對應的網絡持久訂閱。默認是啓動時激活 3.decreaseNetworkConsumerPriority: 默認是false。設定消費者優先權,如果爲true,網絡的消費者優先級降低爲-5。如果爲false,則默認跟本地消費者一樣爲0 4.networkTTL: 默認是1,網絡中用於消息和訂閱消費的broker數量 5.messageTTL: 默認是1,網絡中用於消息的broker數量 6.consumerTTL: 默認是1,網絡中用於消費的broker數量 7.conduitSubscriptions: 默認true,是否把同一個broker的多個consumer當做一個來處理(在做集羣的時候如果有多個consumer,需要設置爲false) 8.dynamicallyIncludedDestinations:默認爲空,要包括的動態消息地址,類適於excludedDestinations; 以下主要是參照示例 |
networkConnector配置的可用屬性
Broker1
Broker2
Broker3
完成以上配置然後重啓各個Broker
驗證是否配置成功
直接打開MQ的WEB控制檯,默認地址 http://10.10.1.44:8161/ 到network的菜單項可以看到如下效果
然後到另外兩個控制檯查看是否也有如下配置,如果都有顯示說明已經配置好,如果沒有說明哪裏配置的有問題,請參照上面步驟檢查。
4. 消息驗證
4.1、生產者客戶端發送消息到Broker1,Consumer客戶端從Broker3或Broker2上面訂閱消息
示例代碼: Producer
public class MQProducer {
public static void main(String[] args) throws JMSException {
// 連接到ActiveMQ服務器
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("tcp://server1.com:61616");
Connection connection = factory.createConnection();
connection.start();
Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
// 創建主題
Topic topic = session.createTopic("VirtualTopic.TEST");
MessageProducer producer = session.createProducer(topic);
// NON_PERSISTENT 非持久化 PERSISTENT 持久化,發送消息時用使用持久模式
producer.setDeliveryMode(DeliveryMode.PERSISTENT);
TextMessage message = session.createTextMessage();
for (int i = 0; i < 4; i++) {
message.setText("topic 消息。"+i);
message.setStringProperty("property", "消息Property");
// 發佈主題消息
producer.send(message);
}
System.out.println("消息發送完畢!");
session.close();
connection.close();
}
}
示例代碼:Consume
public class MQConsumer {
public static void main(String[] args) throws JMSException {
// 連接到ActiveMQ服務器
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("tcp://server1.com:61618");
Connection connection = factory.createConnection();
connection.start();
Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
// 創建主題
Queue topicA = session.createQueue("Consumer.A.VirtualTopic.TEST");
Queue topicB = session.createQueue("Consumer.B.VirtualTopic.TEST");
// 創建消費者A組1-3號訂閱
String port = "61618-";
createConsumerQueue(port + "A1", topicA, session);
// createConsumerQueue(port + "A2", topicA, session);
// createConsumerQueue("A3", topicA, session);
// 創建消費者B組1-3號訂閱
// createConsumerQueue("B1", topicB, session);
// createConsumerQueue("B2", topicB, session);
// createConsumerQueue("B3", topicB, session);
}
private static void createConsumerQueue(String name, Queue topic, Session session) throws JMSException {
// 創建消費者A組訂閱
MessageConsumer consumerA1 = session.createConsumer(topic);
consumerA1.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message msg) {
try {
System.out.println(msg.getJMSType());
if (msg instanceof TextMessage) {
TextMessage tm = (TextMessage) msg;
System.out.println("Received message " + name + ": " + tm.getText() + ":"
+ tm.getStringProperty("property"));
} else if (msg instanceof ActiveMQBytesMessage) {
ActiveMQBytesMessage bytesMessage = (ActiveMQBytesMessage) msg;
if (bytesMessage != null) {
byte[] bt = new byte[(int) bytesMessage.getBodyLength()];
bytesMessage.readBytes(bt);
System.out.println("Received message " + name + ": " +new String(bt));
}
}
} catch (JMSException e1) {
e1.printStackTrace();
}
}
});
}
}
以上,先運行Consumer 61618 和61617,再運行Producer;可以看到消息被兩個客戶端均勻消費
61617
61618
通過以上可以發現,集羣搭建已經生效