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
通过以上可以发现,集群搭建已经生效
应用说明:
完成以上配置,在实际运用过程中可能会存在如下问题
重复消息消费:
往某个虚拟主题里面发送多条消息(比如10条),然后开多个客户端,每个客户端连接到不同的Broker。到时候可以观察到消息被重复消费,每台服务器都能收到消息并且有相同的.
解决方案:
发现这个问题需要在配置参数中加入如下配置
<networkConnectors>
<networkConnector uri="static:(tcp://127.0.0.1:61617,tcp://127.0.0.1:61618)" duplex="true">
<excludedDestinations>
<topic physicalName="VirtualTopic.*" />
</excludedDestinations>
</networkConnector>
</networkConnectors>
针对以上补充说明networkConnector的配置说明
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,如:
<dynamicallyIncludedDestinations>
<queue physicalName="include.test.foo"/>
<topic physicalName="include.test.bar"/>
</dynamicallyIncludedDestinations>
9.staticallyIncludedDestinations:默认为空,要包括的静态消息地址。类似于excludedDestinations,如:
<staticallyIncludedDestinations>
<queue physicalName="always.include.queue"/>
</staticallyIncludedDestinations>
10.excludedDestinations: 默认为空,指定排除的地址,示例如下:
<networkConnectors>
<networkConnector uri="static://(tcp://localhost:61617)" name="bridge" dynamicOnly="false" conduitSubscriptions="true" decreaseNetworkConsumerPriority="false">
<excludedDestinations>
<queue physicalName="exclude.test.foo">
<topic physicalName="exclude.test.bar">
</excludedDestinations>
<dynamicallyIncludedDestinations>
<queue physicalName="include.test.foo"/>
<topic physicalName="include.test.bar"/>
</dynamicallyIncludedDestinations>
<staticallyIncludedDestinations>
<queue physicalName="always.include.queue"/>
</staticallyIncludedDestinations>
</networkConnector>
</networkConnectors>
12. prefetchSize: 默认是1000,持有的未确认的最大消息数量,必须大于0,因为网络消费者不能自己轮询消息
13. suppressDuplicateQueueSubscriptions: 默认false,如果为true,重复的订阅关系一产生即被阻止
14. bridgeTempDestinations: 默认true,是否广播advisory messages来创建临时的destination
15. alwaysSyncSend: 默认false,如果为true,非持久化消息也将使用request/reply方式代替oneway方式发送到远程broker
16. staticBridge: 默认false,如果为true,只有staticallyIncludedDestinations中配置的destination可以被处理