ActiveMQ有很多种集群方式,包括Queue consumer clusters、Broker clusters、Discovery of brokers、Networks of brokers、Master Slave、Replicated Message Stores。
其中Master Slave集群有四种实现方式,包括Pure Master Slave、Shared File System Master Slave、JDBC Master Slave和Replicated LevelDB Store。
下面详细介绍这些集群方式的实现过程。
一、Queue consumer clusters
一个队列有多个消费者,每个消费者是平等的,他们一起竞争队列中的消息,当一个消费者挂掉时,消息会被其他消费者接收,这样可以实现队列消息的高可用性和负载均衡。
二、Broker clusters
联合多个Broker结点实现集群,使用故障转移协议failover,当集群中的broker结点挂掉时,客户端会自动连接到集群中的其他broker结点。
三、Discovery of brokers
通过静态发送或动态发现,来自动发现集群中的brokers,客户端能自动检测和连接集群中的broker.
四、Networks of brokers
这种集群方式可以支持某个broker中的消费者去接收其他broker中的消息,从而实现负载均衡、提高系统吞吐量。
比如客户端clientA发送消息到brokerA上的队列queueA,客户端clientB连接brokerB上的队列queueA,这时brokerA上队列queueA的消息就会自动路由到brokerB上的队列queueA上,也就是说clientB可以间接读取brokerA的队列queueA上的消息。
下面创建一个包含三个broker的Network of brokers集群。
1.修改三个broker节点的配置文件activemq.xml
<broker xmlns="http://activemq.apache.org/schema/core" brokerName="network_of_brokers_01"> <networkConnectors> <networkConnector uri="static:(tcp://localhost:61616,tcp://localhost:61617,tcp://localhost:61618)" name="bridge01" duplex="true" dynamicOnly="false" conduitSubscriptions="true" decreaseNetworkConsumerPriority="false" /> </networkConnectors> <transportConnectors> <transportConnector name="openwire" uri="tcp://0.0.0.0:61616"/> </transportConnectors> </broker>
<broker xmlns="http://activemq.apache.org/schema/core" brokerName="network_of_brokers_02"> <networkConnectors> <networkConnector uri="static:(tcp://localhost:61616,tcp://localhost:61617,tcp://localhost:61618)" name="bridge01" duplex="true" dynamicOnly="false" conduitSubscriptions="true" decreaseNetworkConsumerPriority="false" /> </networkConnectors> <transportConnectors> <transportConnector name="openwire" uri="tcp://0.0.0.0:61617"/> </transportConnectors> </broker>
<broker xmlns="http://activemq.apache.org/schema/core" brokerName="network_of_brokers_01"> <networkConnectors> <networkConnector uri="static:(tcp://localhost:61616,tcp://localhost:61617,tcp://localhost:61618)" name="bridge01" duplex="true" dynamicOnly="false" conduitSubscriptions="true" decreaseNetworkConsumerPriority="false" /> </networkConnectors> <transportConnectors> <transportConnector name="openwire" uri="tcp://0.0.0.0:61618"/> </transportConnectors> </broker>
2.消息接收者
package com.activemq; import java.util.Date; import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.MessageConsumer; import javax.jms.Session; import javax.jms.TextMessage; import org.apache.activemq.ActiveMQConnection; import org.apache.activemq.ActiveMQConnectionFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * ActiveMQ 接收消息 * @author brushli * @date 2014-12-05 */ public class Receiver { private static final Logger logger = LoggerFactory.getLogger(Receiver.class); public void receiveMessage(){ Connection connection = null; Session session = null; try { ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, ActiveMQConnection.DEFAULT_PASSWORD, "failover:(tcp://127.0.0.1:61616,tcp://127.0.0.1:61617,tcp://127.0.0.1:61618)"); connection = connectionFactory.createConnection(); connection.start(); //(parameter a:是否支持事务,parameter b:消息应答模式) session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE); Destination destination = session.createQueue("cluster_queue"); MessageConsumer consumer = session.createConsumer(destination); logger.info("begin to receive Message! currentTime="+new Date().toLocaleString()); while (true) { //设置接收者接收消息的时间,为了便于测试,这里谁定为100s TextMessage message = (TextMessage) consumer.receive(); if (null != message) { logger.info("receive message[" + message.getText()+"] "); } } } catch (JMSException e) { e.printStackTrace(); }finally{ try { session.close(); connection.close(); } catch (JMSException e) { e.printStackTrace(); } } } public static void main(String[] args) { Receiver receiver = new Receiver(); receiver.receiveMessage(); } }
3.消息发送者
package com.activemq; import java.util.Date; import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.DeliveryMode; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.MessageProducer; import javax.jms.Session; import javax.jms.TextMessage; import org.apache.activemq.ActiveMQConnection; import org.apache.activemq.ActiveMQConnectionFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * ActiveMQ 发送消息 * @author brushli * @date 2014-12-05 */ public class Sender { private static final Logger logger = LoggerFactory.getLogger(Sender.class); public void sendMessage(){ try { ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, ActiveMQConnection.DEFAULT_PASSWORD, "failover:(tcp://127.0.0.1:61616,tcp://127.0.0.1:61617,tcp://127.0.0.1:61618)"); Connection connection = connectionFactory.createConnection(); connection.start(); //(parameter a:是否支持事务,parameter b:消息应答模式) Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE); Destination destination = session.createQueue("cluster_queue"); //创建什么什么模式的生产者,可选参数: Queue, TemporaryQueue, TemporaryTopic, Topic MessageProducer producer = session.createProducer(destination); producer.setDeliveryMode(DeliveryMode.PERSISTENT); logger.info("begin to send Message! currentTime="+new Date().toLocaleString()); for(int i=1; i<=10; i++) { TextMessage message = session.createTextMessage(); message.setText("message" + i); producer.send(message); Thread.sleep(5000); logger.info("send message [" + "message" + i +"]"); } logger.info("send Message finished! currentTime="+new Date().toLocaleString()); session.close(); connection.close(); } catch (JMSException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { Sender sender = new Sender(); sender.sendMessage(); } }
4.测试步骤
(1)启动三个消费者
ActiveMQ01 broker01
2014-12-10 10:04:05 INFO FailoverTransport.doReconnect: 1030 - Successfully connected to tcp://127.0.0.1:61616
ActiveMQ02 broker02
2014-12-10 10:04:07 INFO FailoverTransport.doReconnect: 1030 - Successfully connected to tcp://127.0.0.1:61617
ActiveMQ03 broker03
2014-12-10 10:04:08 INFO FailoverTransport.doReconnect: 1030 - Successfully connected to tcp://127.0.0.1:61618
(2)启动发送者
发送十条消息
2014-12-10 10:04:10 INFO FailoverTransport.doReconnect: 1030 - Successfully connected to tcp://127.0.0.1:61618 2014-12-10 10:04:10 INFO Sender.sendMessage: 39 - begin to send Message! currentTime=2014-12-10 10:04:10 2014-12-10 10:04:15 INFO Sender.sendMessage: 45 - send message [message1] 2014-12-10 10:04:20 INFO Sender.sendMessage: 45 - send message [message2] 2014-12-10 10:04:25 INFO Sender.sendMessage: 45 - send message [message3] 2014-12-10 10:04:30 INFO Sender.sendMessage: 45 - send message [message4] 2014-12-10 10:04:35 INFO Sender.sendMessage: 45 - send message [message5] 2014-12-10 10:04:40 INFO Sender.sendMessage: 45 - send message [message6] 2014-12-10 10:04:45 INFO Sender.sendMessage: 45 - send message [message7] 2014-12-10 10:04:50 INFO Sender.sendMessage: 45 - send message [message8] 2014-12-10 10:04:55 INFO Sender.sendMessage: 45 - send message [message9] 2014-12-10 10:05:00 INFO Sender.sendMessage: 45 - send message [message10] 2014-12-10 10:05:00 INFO Sender.sendMessage: 47 - send Message finished! currentTime=2014-12-10 10:05:00
(3)三个消费者接收消息
消费者01,连接ActiveMQ01 broker01
2014-12-10 10:04:05 INFO FailoverTransport.doReconnect: 1030 - Successfully connected to tcp://127.0.0.1:61616 2014-12-10 10:04:05 INFO Receiver.receiveMessage: 39 - begin to receive Message! currentTime=2014-12-10 10:04:05 2014-12-10 10:04:10 INFO Receiver.receiveMessage: 44 - receive message[message1] 2014-12-10 10:04:15 INFO Receiver.receiveMessage: 44 - receive message[message2] 2014-12-10 10:04:35 INFO Receiver.receiveMessage: 44 - receive message[message6] 2014-12-10 10:04:40 INFO Receiver.receiveMessage: 44 - receive message[message7]
消费者02,连接ActiveMQ02 broker02
2014-12-10 10:04:07 INFO FailoverTransport.doReconnect: 1030 - Successfully connected to tcp://127.0.0.1:61617 2014-12-10 10:04:07 INFO Receiver.receiveMessage: 39 - begin to receive Message! currentTime=2014-12-10 10:04:07 2014-12-10 10:04:20 INFO Receiver.receiveMessage: 44 - receive message[message3] 2014-12-10 10:04:25 INFO Receiver.receiveMessage: 44 - receive message[message4] 2014-12-10 10:04:45 INFO Receiver.receiveMessage: 44 - receive message[message8] 2014-12-10 10:04:50 INFO Receiver.receiveMessage: 44 - receive message[message9]
消费者03,连接ActiveMQ03 broker03
2014-12-10 10:04:08 INFO FailoverTransport.doReconnect: 1030 - Successfully connected to tcp://127.0.0.1:61618 2014-12-10 10:04:08 INFO Receiver.receiveMessage: 39 - begin to receive Message! currentTime=2014-12-10 10:04:08 2014-12-10 10:04:30 INFO Receiver.receiveMessage: 44 - receive message[message5] 2014-12-10 10:04:55 INFO Receiver.receiveMessage: 44 - receive message[message10]
5.总结
发送十条消息到broker01的队列cluster_queue后,三个消费者分别从broker01,broker02,broker03的队列cluster_queue获取到了消息。
五、Master Slave
5.1Pure Master Slave
这是一种比较简单的主从集群方式,一个主点,一个备点,备点的数据只是主点数据的拷贝,当主点挂掉后,会主动启用备点,消息发送到备点,消费者也改从备点获取消息。
这种方式的优点是:实现简单
缺点是:
(1)只能有一个备点
(2)当主点挂掉后,若需要再启动主点,必须停止备点
(3)没有主备数据同步机制,必须手动把备点的数据文件拷贝到主点
由于Pure Master Slave集群方式有这么多缺点,在ActiveMQ5.8的版本及之后的版本,这种集群方式就取消了。
实现方式如下:
(1)Master的配置文件作如下修改
<brokerbrokerName="pure_master"> <kahaDB directory="${activemq.data}/kahadb_pure_master"/> <transportConnector name="openwire" uri="tcp://0.0.0.0:61616"/>
(2)Slave的配置文件作如下修改
<broker brokerName="pure_slave" masterConnectorURI="tcp://0.0.0.0:61616" shutdownOnMasterFailure="false"> <kahaDB directory="${activemq.base}/data/kahadb_pure_slave "/> <transportConnector name="openwire" uri="tcp://0.0.0.0:61617"/>
(3)启动Master
apache-activemq-5.7.0_master\bin\activemq.bat
(4)启动Slave
apache-activemq-5.7.0_Slave\bin\activemq.bat
(5)生产者
package com.activemq; import java.util.Date; import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.DeliveryMode; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.MessageProducer; import javax.jms.Session; import javax.jms.TextMessage; import org.apache.activemq.ActiveMQConnection; import org.apache.activemq.ActiveMQConnectionFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * ActiveMQ 发送消息 * @author brushli * @date 2014-12-06 */ public class Sender { private static final Logger logger = LoggerFactory.getLogger(Sender.class); public void sendMessage(){ try { ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, ActiveMQConnection.DEFAULT_PASSWORD, "failover:(tcp://127.0.0.1:61616,tcp://127.0.0.1:61617)"); Connection connection = connectionFactory.createConnection(); connection.start(); //(parameter a:是否支持事务,parameter b:消息应答模式) Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE); Destination destination = session.createQueue("myQueue"); //创建什么什么模式的生产者,可选参数: Queue, TemporaryQueue, TemporaryTopic, Topic MessageProducer producer = session.createProducer(destination); producer.setDeliveryMode(DeliveryMode.PERSISTENT); logger.info("begin to send Message! currentTime="+new Date().toLocaleString()); for(int i=1; i<=10; i++) { TextMessage message = session.createTextMessage(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } message.setText("" + i); producer.send(message); logger.info("already send "+i); } logger.info("send Message finished! currentTime="+new Date().toLocaleString()); session.close(); connection.close(); } catch (JMSException e) { e.printStackTrace(); } } public static void main(String[] args) { Sender sender = new Sender(); sender.sendMessage(); } }
(6)消费者
package com.activemq; import java.util.Date; import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.MessageConsumer; import javax.jms.Session; import javax.jms.TextMessage; import org.apache.activemq.ActiveMQConnection; import org.apache.activemq.ActiveMQConnectionFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * ActiveMQ 接收消息 * @author brushli * @date 2014-12-06 */ public class Receiver { private static final Logger logger = LoggerFactory.getLogger(Sender.class); public void receiveMessage(){ Connection connection = null; Session session = null; try { ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, ActiveMQConnection.DEFAULT_PASSWORD, "failover:(tcp://127.0.0.1:61616,tcp://127.0.0.1:61617)"); connection = connectionFactory.createConnection(); connection.start(); //(parameter a:是否支持事务,parameter b:消息应答模式) session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE); Destination destination = session.createQueue("myQueue"); MessageConsumer consumer = session.createConsumer(destination); logger.info("begin to receive Message! currentTime="+new Date().toLocaleString()); while (true) { //设置接收者接收消息的时间,为了便于测试,这里谁定为100s TextMessage message = (TextMessage) consumer.receive(); if (null != message) { logger.info(message.getText()); } } } catch (JMSException e) { e.printStackTrace(); }finally{ try { session.close(); connection.close(); } catch (JMSException e) { e.printStackTrace(); } } } public static void main(String[] args) { Receiver receiver = new Receiver(); receiver.receiveMessage(); } }
(7)测试过程
1).启动生产者,发送消息
2).启动消费者,可看到接收到的消息
3).关闭消费者
4).生产者继续发送消息A
5).停止Master(可看到生产者端显示连接到Slave(tcp://0.0.0.0:61617)了)
6).生产者继续发送消息B
7).启动消费者
8).消费者接收了消息A和消息B,可见Slave接替了Master的工作,而且储存了之前生产者经过Master发送的消息
生产者的日志如下:
14-12-06 22:50:10 INFO FailoverTransport.doReconnect - Successfully connected to tcp://127.0.0.1:61616 14-12-06 22:50:10 INFO Sender.sendMessage - begin to send Message! currentTime=2014-12-6 22:50:10 14-12-06 22:50:14 INFO Sender.sendMessage - already send 1 14-12-06 22:50:17 INFO Sender.sendMessage - already send 2 14-12-06 22:50:21 INFO Sender.sendMessage - already send 3 14-12-06 22:50:25 INFO Sender.sendMessage - already send 4 14-12-06 22:50:28 INFO Sender.sendMessage - already send 5 14-12-06 22:50:30 WARN FailoverTransport.handleTransportFailure - Transport (tcp://127.0.0.1:61616) failed, reason: java.net.SocketException: Connection reset, attempting to automatically reconnect 14-12-06 22:50:31 INFO FailoverTransport.doReconnect - Successfully reconnected to tcp://127.0.0.1:61617 14-12-06 22:50:31 INFO Sender.sendMessage - already send 6 14-12-06 22:50:34 INFO Sender.sendMessage - already send 7 14-12-06 22:50:38 INFO Sender.sendMessage - already send 8 14-12-06 22:50:41 INFO Sender.sendMessage - already send 9 14-12-06 22:50:44 INFO Sender.sendMessage - already send 10 14-12-06 22:50:44 INFO Sender.sendMessage - send Message finished! currentTime=2014-12-6 22:50:44
消费者的日志如下:
14-12-06 22:54:13 INFO FailoverTransport.doReconnect - Successfully connected to tcp://127.0.0.1:61617 14-12-06 22:54:13 INFO Receiver.receiveMessage - begin to receive Message! currentTime=2014-12-6 22:54:13 14-12-06 22:54:13 INFO Receiver.receiveMessage - 1 14-12-06 22:54:13 INFO Receiver.receiveMessage - 2 14-12-06 22:54:13 INFO Receiver.receiveMessage - 3 14-12-06 22:54:13 INFO Receiver.receiveMessage - 4 14-12-06 22:54:13 INFO Receiver.receiveMessage - 5 14-12-06 22:54:13 INFO Receiver.receiveMessage - 6 14-12-06 22:54:13 INFO Receiver.receiveMessage - 7 14-12-06 22:54:13 INFO Receiver.receiveMessage - 8 14-12-06 22:54:13 INFO Receiver.receiveMessage - 9 14-12-06 22:54:13 INFO Receiver.receiveMessage - 10
可以看出,在主点61616关闭后,生产者和消费者会主动去连接备点61617,这样就实现了消息的双机热备功能。
5.2、Shared File System Master Slave
利用共享文件系统:当多台机器上都部署了AMQ时,指定这些机器的一个共享的文件路径作为存储。
存储默认是基于AMQ的kahaDB(底层是文件系统)实现。
当一个AMQ实例获得了共享文件的锁,这个实例就成为了Master,其它实例即为Slave。如果这时Master挂了,其它AMQ实例会竞争共享文件的锁,获得锁的就成为Master,其它实例还是Slave。部署时Slave没有限制数,而且自动切换Master不需要人工干预。
我们设置三个ActiveMQ,61616,61617,61618
(1)修改配置文件activemq.xml,指一共享的文件路径
1)ActiveMQ1
<persistenceAdapter> <kahaDB directory="D:\shareBrokerData"/> </persistenceAdapter> <transportConnector name="openwire" uri="tcp://0.0.0.0:61616"/>
2)ActiveMQ2
<persistenceAdapter> <kahaDB directory="D:\shareBrokerData"/> </persistenceAdapter> <transportConnector name="openwire" uri="tcp://0.0.0.0:61617"/>
3)ActiveMQ3
<persistenceAdapter> <kahaDB directory="D:\shareBrokerData"/> </persistenceAdapter> <transportConnector name="openwire" uri="tcp://0.0.0.0:61618"/>
(2)修改生产者的连接
failover:(tcp://127.0.0.1:61616,tcp://127.0.0.1:61617,tcp://127.0.0.1:61618)
(3)修改消费者的连接
failover:(tcp://127.0.0.1:61616,tcp://127.0.0.1:61617,tcp://127.0.0.1:61618)
(4)测试步骤
1.开启ActiveMQ1 61616
2.开启ActiveMQ2 61617
3.开启ActiveMQ3 61618
4.开启生产者
发送消息1,消息2
5.关闭ActiveMQ1
发送消息3,消息4
6.关闭ActiveMQ2
发送消息5,消息6
7.关闭ActiveMQ3
等待...
8.开启ActiveMQ1 61616
连接ActiveMQ1,发送消息7、8、9、10
9.开启消费者
消费消息1、2、3、4、5、6、7、8、9、10
10.生产者的运行日志
14-12-07 23:07:15 INFO FailoverTransport.doReconnect - Successfully connected to tcp://127.0.0.1:61616 14-12-07 23:07:15 INFO Sender.sendMessage - begin to send Message! currentTime=2014-12-7 23:07:15 14-12-07 23:07:20 INFO Sender.sendMessage - already send 1 14-12-07 23:07:25 INFO Sender.sendMessage - already send 2 14-12-07 23:07:27 WARN FailoverTransport.handleTransportFailure - Transport (tcp://127.0.0.1:61616) failed, reason: java.net.SocketException: Connection reset, attempting to automatically reconnect 14-12-07 23:07:34 INFO FailoverTransport.doReconnect - Successfully reconnected to tcp://127.0.0.1:61617 14-12-07 23:07:34 INFO Sender.sendMessage - already send 3 14-12-07 23:07:39 INFO Sender.sendMessage - already send 4 14-12-07 23:07:40 WARN FailoverTransport.handleTransportFailure - Transport (tcp://127.0.0.1:61617) failed, reason: java.io.EOFException, attempting to automatically reconnect 14-12-07 23:07:47 INFO FailoverTransport.doReconnect - Successfully reconnected to tcp://127.0.0.1:61618 14-12-07 23:07:47 INFO Sender.sendMessage - already send 5 14-12-07 23:07:52 INFO Sender.sendMessage - already send 6 14-12-07 23:07:54 WARN FailoverTransport.handleTransportFailure - Transport (tcp://127.0.0.1:61618) failed, reason: java.net.SocketException: Connection reset, attempting to automatically reconnect 14-12-07 23:08:04 INFO FailoverTransport.doReconnect - Successfully reconnected to tcp://127.0.0.1:61616 14-12-07 23:08:04 INFO Sender.sendMessage - already send 7 14-12-07 23:08:09 INFO Sender.sendMessage - already send 8 14-12-07 23:08:14 INFO Sender.sendMessage - already send 9 14-12-07 23:08:20 INFO Sender.sendMessage - already send 10 14-12-07 23:08:20 INFO Sender.sendMessage - send Message finished! currentTime=2014-12-7 23:08:20
从日志中可以看出,生产者启动的时候是连接ActiveMQ1 61616;
当ActiveMQ1关闭时,生产者自动去连接ActiveMQ2 61617,消息发送至ActiveMQ2;
当ActiveMQ2关闭时,生产者自动去连接ActiveMQ3 61618,消息发送至ActiveMQ3;
当ActiveMQ3关闭时,生产者会一直等待;
当再次启动ActiveMQ1时,生产者自动去连接ActiveMQ1 61616,消息发送至ActiveMQ1;
11.消费者的运行日志
14-12-07 23:15:04 INFO FailoverTransport.doReconnect - Successfully connected to tcp://127.0.0.1:61616 14-12-07 23:15:04 INFO Receiver.receiveMessage - begin to receive Message! currentTime=2014-12-7 23:15:04 14-12-07 23:15:04 INFO Receiver.receiveMessage - 1 14-12-07 23:15:04 INFO Receiver.receiveMessage - 2 14-12-07 23:15:04 INFO Receiver.receiveMessage - 3 14-12-07 23:15:04 INFO Receiver.receiveMessage - 4 14-12-07 23:15:04 INFO Receiver.receiveMessage - 5 14-12-07 23:15:04 INFO Receiver.receiveMessage - 6 14-12-07 23:15:04 INFO Receiver.receiveMessage - 7 14-12-07 23:15:04 INFO Receiver.receiveMessage - 8 14-12-07 23:15:04 INFO Receiver.receiveMessage - 9 14-12-07 23:15:04 INFO Receiver.receiveMessage - 10
启动消费者时,消费者连接ActiveMQ1,并读取所有消息
12.作了集群后的测试
(1). 测试环境
操作系统:Win7 旗舰版64位
CPU:Intel(R) Core(TM) i5-3470 CPU @ 3.2GHz
JAVA:JDK1.6
ActiveMQ:ActiveMQ5.7.0
修改配置文件/conf/activemq.xml
<persistenceAdapter> <kahaDB directory="D:\shareBrokerData" indexCacheSize="100000" indexWriteBatchSize="1000" enableJournalDiskSyncs="false" journalMaxFileLength="128mb" concurrentStoreAndDispatchQueues="true" concurrentStoreAndDispatchTopics="true"/> </persistenceAdapter>
(2)测试步骤
单个生产者向事务/非事务,持久化/非持久化 四个队列分别插入100W条记录,统计所需要的时间.
(3)测试代码
package com.activemq; import java.util.Date; import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.DeliveryMode; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.MapMessage; import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.Session; import org.apache.activemq.ActiveMQConnection; import org.apache.activemq.ActiveMQConnectionFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ActivemqExample { private static final Logger logger = LoggerFactory.getLogger(ActivemqExample.class); private ConnectionFactory connectionFactory = null; private Connection connection = null; public void initActiveMQ(){ connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, ActiveMQConnection.DEFAULT_PASSWORD, "failover:(tcp://127.0.0.1:61616,tcp://127.0.0.1:61617,tcp://127.0.0.1:61618)"); } public void sendTransactedMessage(int persistent){ try { connection = connectionFactory.createConnection(); connection.start(); //(parameter a:是否支持事务,parameter b:消息应答模式) Session session = connection.createSession(Boolean.TRUE, Session.AUTO_ACKNOWLEDGE); String persistentString = (persistent == DeliveryMode.PERSISTENT ? "persistent" : "non-persistent"); String queueName="queue-transacted-"+persistentString; Destination destination = session.createQueue(queueName); //创建什么什么模式的生产者,可选参数: Queue, TemporaryQueue, TemporaryTopic, Topic MessageProducer producer = session.createProducer(destination); producer.setDeliveryMode(persistent); logger.info("begin to send Message! currentTime="+new Date().toLocaleString()); long beginTime = System.currentTimeMillis(); int totalCount = 1000000; for(int i=1; i<=totalCount; i++) { MapMessage message = session.createMapMessage(); message.setLong("count", new Date().getTime()); //通过消息生产者发出消息 producer.send(message); if(i%100 == 0){ session.commit(); //支持事务 if(i%100000 == 0){ logger.info("已发送["+i+"]条"+(persistent == DeliveryMode.PERSISTENT ? "persistent" : "non-persistent")+"消息!"); } } } long endTime = System.currentTimeMillis(); logger.info("send Message finished! currentTime="+new Date().toLocaleString()); logger.info("send Transacted "+persistentString+" Message,100/pertime,totalCount="+totalCount+",cost time="+(endTime-beginTime)/1000+"s,speed="+(totalCount/((endTime-beginTime)/1000))+"/s"); session.close(); connection.close(); } catch (JMSException e) { e.printStackTrace(); } } public void sendNonTransactedMessage(int persistent){ try { connection = connectionFactory.createConnection(); connection.start(); //(parameter a:是否支持事务,parameter b:消息应答模式) Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE); String persistentString = (persistent == DeliveryMode.PERSISTENT ? "persistent" : "non-persistent"); String queueName="queue-non-transacted-"+persistentString; Destination destination = session.createQueue(queueName); //创建什么什么模式的生产者,可选参数: Queue, TemporaryQueue, TemporaryTopic, Topic MessageProducer producer = session.createProducer(destination); producer.setDeliveryMode(persistent); logger.info("begin to send Message! currentTime="+new Date().toLocaleString()); long beginTime = System.currentTimeMillis(); int totalCount = 1000000; for(int i=1; i<=totalCount; i++) { MapMessage message = session.createMapMessage(); message.setLong("count", new Date().getTime()); //通过消息生产者发出消息 producer.send(message); if(i%100000 == 0){ logger.info("已发送["+i+"]条"+(persistent == DeliveryMode.PERSISTENT ? "persistent" : "non-persistent")+"消息!"); } } long endTime = System.currentTimeMillis(); logger.info("send Message finished! currentTime="+new Date().toLocaleString()); logger.info("send Non Transacted "+persistentString+" Message,1/pertime,totalCount="+totalCount+",cost time="+(endTime-beginTime)/1000+"s,speed="+(totalCount/((endTime-beginTime)/1000))+"/s"); session.close(); connection.close(); } catch (JMSException e) { e.printStackTrace(); } } public void receiveMessage(){ try { connection = connectionFactory.createConnection(); connection.start(); //(parameter a:是否支持事务,parameter b:消息应答模式) Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE); Destination destination = session.createQueue("queue-transacted-persistent"); MessageConsumer consumer = session.createConsumer(destination); logger.info("begin to receive Message! currentTime="+new Date().toLocaleString()); long beginTime = System.currentTimeMillis(); int totalCount = 0; while (true && totalCount < 1000000) { //设置接收者接收消息的时间,为了便于测试,这里谁定为100s MapMessage message = (MapMessage) consumer.receive(); if (null != message) { totalCount++; if(totalCount % 100000 == 0){ System.out.println("已收到["+totalCount+"]条消息"); } } } long endTime = System.currentTimeMillis(); logger.info("receive transacted persistent Message,1/pertime,totalCount="+totalCount+",cost time="+(endTime-beginTime)/1000+"s,speed="+(totalCount/((endTime-beginTime)/1000))+"/s"); } catch (JMSException e) { e.printStackTrace(); } } /** * @param args */ public static void main(String[] args) { ActivemqExample activemqExample = new ActivemqExample(); activemqExample.initActiveMQ(); activemqExample.sendTransactedMessage(DeliveryMode.PERSISTENT); activemqExample.sendTransactedMessage(DeliveryMode.NON_PERSISTENT); activemqExample.sendNonTransactedMessage(DeliveryMode.PERSISTENT); activemqExample.sendNonTransactedMessage(DeliveryMode.NON_PERSISTENT); // activemqExample.receiveMessage(); } }
(4)性能测试报告
主备Shared File System Master Slave集群的测试报告
1)第一次发送
支持事务 |
支持持久化 |
测试数据 |
花费时间 |
速度 |
是 |
是 |
100W |
106s |
9433/s |
是 |
否 |
100W |
62s |
16129/s |
否 |
是 |
100W |
106s |
9433/s |
否 |
否 |
100W |
22s |
45454/s |
2)第二次发送
支持事务 |
支持持久化 |
测试数据 |
花费时间 |
速度 |
是 |
是 |
100W |
119s |
8403/s |
是 |
否 |
100W |
57s |
17543/s |
否 |
是 |
100W |
119s |
8403/s |
否 |
否 |
100W |
24s |
41666/s |
3)第三次发送
支持事务 |
支持持久化 |
测试数据 |
花费时间 |
速度 |
是 |
是 |
100W |
123s |
8130/s |
是 |
否 |
100W |
62s |
16129/s |
否 |
是 |
100W |
132s |
7575/s |
否 |
否 |
100W |
20s |
50000/s |
(5)中断测试
Shared File System Master Slave集群中断测试报告
测试步骤:向各个队列发送100W条记录,中间关闭当前连接的ActiveMQ,重新连接其他ActiveMQ,查看测试结果.
支持事务 |
支持持久化 |
结果 |
是 |
是 |
中断前发送的消息有效,中断后重新连接ActiveMQ,剩余的队列数据并不会重新发送. 中断前发送的消息有效,中断后不会重新发送消息. |
是 |
否 |
中断前发送的消息会回滚,消息全部删除,中断后重新连接ActiveMQ,剩余的队列消息并不会重新发送. 所有消息丢失! |
否 |
是 |
中断前发送的消息有效,重新连接ActiveMQ后剩余的队列消息会重新发送. 所有消息有效! |
否 |
否 |
中断前发送的消息会回滚,消息全部删除,重新连接ActiveMQ,剩余的队列消息会重新发送. 中断前发送的消息无效,中断后发送的消息有效! |
5.3、JDBC Master Slave
其实与Shared File System一样,只是把共享文件系统换成数据库作为存储。方便实用,但要保证数据库的高可用性。
1、修改三个activemq的配置文件activemq.xml
<broker brokerName="jdbc_broker1" dataDirectory="${activemq.data}" useJmx="true"> <persistenceAdapter> <jdbcPersistenceAdapter dataDirectory="${activemq.base}/data" dataSource="#dataSource" createTablesOnStartup="false" /> </persistenceAdapter> </broker> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="com.mysql.jdbc.Driver" /> <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf-8" /> <property name="user" value="root" /> <property name="password" value="root" /> <property name="initialPoolSize" value="10" /> <property name="minPoolSize" value="10" /> <property name="maxPoolSize" value="100" /> </bean>
<broker brokerName="jdbc_broker2" dataDirectory="${activemq.data}" useJmx="true"> <persistenceAdapter> <jdbcPersistenceAdapter dataDirectory="${activemq.base}/data" dataSource="#dataSource" createTablesOnStartup="false" /> </persistenceAdapter> </broker> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="com.mysql.jdbc.Driver" /> <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf-8" /> <property name="user" value="root" /> <property name="password" value="root" /> <property name="initialPoolSize" value="10" /> <property name="minPoolSize" value="10" /> <property name="maxPoolSize" value="100" /> </bean>
<broker brokerName="jdbc_broker3" dataDirectory="${activemq.data}" useJmx="true"> <persistenceAdapter> <jdbcPersistenceAdapter dataDirectory="${activemq.base}/data" dataSource="#dataSource" createTablesOnStartup="false" /> </persistenceAdapter> </broker> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="com.mysql.jdbc.Driver" /> <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf-8" /> <property name="user" value="root" /> <property name="password" value="root" /> <property name="initialPoolSize" value="10" /> <property name="minPoolSize" value="10" /> <property name="maxPoolSize" value="100" /> </bean>
注意:jdbc_broker1中的createTablesOnStartup属性,初次启动时要设为true,ActiveMQ会自动到数据库test中创建三个表activemq_acks,activemq_lock,activemq_msgs,之后就可以设置成false.
2、添加JAR包
添加JAR包mysql-connector-java-5.1.22-bin.jar、c3p0-0.9.1.2.jar到ActiveMQ的安装目录的lib文件下,比如:apache-activemq-5.7.0\lib
3、测试
3.1 集群中断测试
每隔5秒向集群发送一条消息,一共发送10消息,期间关闭服务器ActiveMQ1、ActiveMQ2
测试步骤
(1)开启ActiveMQ1 61616
(2)开启ActiveMQ2 61617
(3)开启ActiveMQ3 61618
(4)开启生产者,连接ActiveMQ1 61616
(5)发送消息1、2、3、4、5、6、7
(6)关闭ActiveMQ1 61616
(7)生产者连接ActiveMQ2 61617
(8)发送消息8、9
(9)关闭ActiveMQ2 61617
(10)生产者连接ActiveMQ3 61618
(11)发送消息11
(12)开启消费者
(13)消费消息1、2、3、4、5、6、7、8、9、10
(14)生产者运行日志
2014-12-08 14:59:12 INFO FailoverTransport.doReconnect: 1030 - Successfully connected to tcp://127.0.0.1:61616 2014-12-08 14:59:12 INFO Sender.sendMessage: 39 - begin to send Message! currentTime=2014-12-8 14:59:12 2014-12-08 14:59:17 INFO Sender.sendMessage: 45 - send message 1 2014-12-08 14:59:22 INFO Sender.sendMessage: 45 - send message 2 2014-12-08 14:59:27 INFO Sender.sendMessage: 45 - send message 3 2014-12-08 14:59:32 INFO Sender.sendMessage: 45 - send message 4 2014-12-08 14:59:37 INFO Sender.sendMessage: 45 - send message 5 2014-12-08 14:59:42 INFO Sender.sendMessage: 45 - send message 6 2014-12-08 14:59:47 INFO Sender.sendMessage: 45 - send message 7 2014-12-08 14:59:48 WARN FailoverTransport.handleTransportFailure: 255 - Transport (tcp://127.0.0.1:61616) failed, reason: java.net.SocketException: Connection reset, attempting to automatically reconnect 2014-12-08 14:59:49 INFO FailoverTransport.doReconnect: 1032 - Successfully reconnected to tcp://127.0.0.1:61617 2014-12-08 14:59:52 INFO Sender.sendMessage: 45 - send message 8 2014-12-08 14:59:57 INFO Sender.sendMessage: 45 - send message 9 2014-12-08 14:59:58 WARN FailoverTransport.handleTransportFailure: 255 - Transport (tcp://127.0.0.1:61617) failed, reason: java.net.SocketException: Connection reset, attempting to automatically reconnect 2014-12-08 14:59:59 INFO FailoverTransport.doReconnect: 1032 - Successfully reconnected to tcp://127.0.0.1:61618 2014-12-08 15:00:02 INFO Sender.sendMessage: 45 - send message 10 2014-12-08 15:00:02 INFO Sender.sendMessage: 47 - send Message finished! currentTime=2014-12-8 15:00:02
(15)消费者运行日志
2014-12-08 15:32:17 INFO FailoverTransport.doReconnect: 1030 - Successfully connected to tcp://127.0.0.1:61618 2014-12-08 15:32:17 INFO Receiver.receiveMessage: 39 - begin to receive Message! currentTime=2014-12-8 15:32:17 2014-12-08 15:32:17 INFO Receiver.receiveMessage: 44 - 1 2014-12-08 15:32:17 INFO Receiver.receiveMessage: 44 - 2 2014-12-08 15:32:17 INFO Receiver.receiveMessage: 44 - 3 2014-12-08 15:32:17 INFO Receiver.receiveMessage: 44 - 4 2014-12-08 15:32:17 INFO Receiver.receiveMessage: 44 - 5 2014-12-08 15:32:17 INFO Receiver.receiveMessage: 44 - 6 2014-12-08 15:32:17 INFO Receiver.receiveMessage: 44 - 7 2014-12-08 15:32:17 INFO Receiver.receiveMessage: 44 - 8 2014-12-08 15:32:17 INFO Receiver.receiveMessage: 44 - 9 2014-12-08 15:32:17 INFO Receiver.receiveMessage: 44 - 10
测试的代码
(1)生产者
package com.activemq; import java.util.Date; import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.DeliveryMode; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.MessageProducer; import javax.jms.Session; import javax.jms.TextMessage; import org.apache.activemq.ActiveMQConnection; import org.apache.activemq.ActiveMQConnectionFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * ActiveMQ 发送消息 * @author brushli * @date 2014-12-05 */ public class Sender { private static final Logger logger = LoggerFactory.getLogger(Sender.class); public void sendMessage(){ try { ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, ActiveMQConnection.DEFAULT_PASSWORD, "failover:(tcp://127.0.0.1:61616,tcp://127.0.0.1:61617,tcp://127.0.0.1:61618)"); Connection connection = connectionFactory.createConnection(); connection.start(); //(parameter a:是否支持事务,parameter b:消息应答模式) Session session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE); Destination destination = session.createQueue("cluster_queue"); //创建什么什么模式的生产者,可选参数: Queue, TemporaryQueue, TemporaryTopic, Topic MessageProducer producer = session.createProducer(destination); producer.setDeliveryMode(DeliveryMode.PERSISTENT); logger.info("begin to send Message! currentTime="+new Date().toLocaleString()); for(int i=1; i<=10; i++) { TextMessage message = session.createTextMessage(); message.setText(""+i); producer.send(message); Thread.sleep(5000); logger.info("send message " + i); } logger.info("send Message finished! currentTime="+new Date().toLocaleString()); session.close(); connection.close(); } catch (JMSException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { Sender sender = new Sender(); sender.sendMessage(); } }
(2)消费者
package com.activemq; import java.util.Date; import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.MessageConsumer; import javax.jms.Session; import javax.jms.TextMessage; import org.apache.activemq.ActiveMQConnection; import org.apache.activemq.ActiveMQConnectionFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * ActiveMQ 接收消息 * @author brushli * @date 2014-12-05 */ public class Receiver { private static final Logger logger = LoggerFactory.getLogger(Receiver.class); public void receiveMessage(){ Connection connection = null; Session session = null; try { ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, ActiveMQConnection.DEFAULT_PASSWORD, "failover:(tcp://127.0.0.1:61616,tcp://127.0.0.1:61617,tcp://127.0.0.1:61618)"); connection = connectionFactory.createConnection(); connection.start(); //(parameter a:是否支持事务,parameter b:消息应答模式) session = connection.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE); Destination destination = session.createQueue("cluster_queue"); MessageConsumer consumer = session.createConsumer(destination); logger.info("begin to receive Message! currentTime="+new Date().toLocaleString()); while (true) { //设置接收者接收消息的时间,为了便于测试,这里谁定为100s TextMessage message = (TextMessage) consumer.receive(); if (null != message) { logger.info(message.getText()); } } } catch (JMSException e) { e.printStackTrace(); }finally{ try { session.close(); connection.close(); } catch (JMSException e) { e.printStackTrace(); } } } public static void main(String[] args) { Receiver receiver = new Receiver(); receiver.receiveMessage(); } }
3.2 集群性能测试
数据库:MySQL5.5.10 ,存储引擎Innodb
测试报告
(1)第一次发送
支持事务 |
支持持久化 |
测试数据 |
花费时间 |
速度 |
是 |
是 |
100W |
288s |
3472/s |
是 |
否 |
100W |
34s |
29411/s |
否 |
是 |
100W |
672s |
1488/s |
否 |
否 |
100W |
18s |
55555/s |
(2)第二次发送
支持事务 |
支持持久化 |
测试数据 |
花费时间 |
速度 |
是 |
是 |
100W |
270s |
3703/s |
是 |
否 |
100W |
32s |
31250/s |
否 |
是 |
100W |
700s |
1428/s |
否 |
否 |
100W |
17s |
58823/s |
(3)第三次发送
支持事务 |
支持持久化 |
测试数据 |
花费时间 |
速度 |
是 |
是 |
100W |
264s |
3787/s |
是 |
否 |
100W |
35s |
28571/s |
否 |
是 |
100W |
733s |
1364/s |
否 |
否 |
100W |
20s |
50000/s |
注意:由于ActiveMQ和数据库MySQL都装在一个主机上,所以速度会慢很多,特别是需要持久化的非事务消息,由于每发送一条消息都需要向数据表activemq_msgs插入一条记录,导致速度很慢。
5.4 Replicated LevelDB Store
ActiveMQ 5.9.0以后,开始支持Replicated LevelDB store方式的主从集群方式。
Replicated LevelDB store使用Zookeeper作为broker节点集群中获取主点的协调中间件,将主点数据保存到LevelDB store,各备点也有各自的LevelDB store,数据会从主点同步到备点。对于Zookeeper的集群,如果集群中有N个节点,那么最多允许(N-1)/2个节点挂掉,集群仍然可用;如果N=3,那么1个节点挂掉集群是可用的,2个节点挂掉,集群就不可用了。关于Zookeeper的集群安装方式请看本人写的另一篇博文: ZooKeeper集群
本人用ActiveMQ连接Zookeeper,一直没成功过,所以该方式的集群待续...
六、Replicated Message Stores(消息复制存储)
6.1 Use RAID disks
6.2 SAN or shared network drive
6.3 Master/Slave
6.4 Clustered JDBC databases
6.5 Use C-JDBC