上一篇介绍了基于ZK的ActiveMQ HA方案,虽然理解起来比较容易,但是有二个不足:

1)  占用的节点数过多,1个zk集群至少3个节点,1个activemq集群也至少得3个节点,但其实正常运行时,只有一个master节点在对外响应,换句话说,花6个节点的成本只为了保证1个activemq master节点的高可用,太浪费资源了。

2)  性能下降太明显,比起单节点的activemq,性能下降了近1个数量级。

这一篇将介绍基于networks of brokers的HA方案,不需要借助zk等第3方组件,只需要2个activemq节点就能达到类似效果,进入正题之前,先来简单谈谈Broker这个概念。

Broker一词的原意是『经纪人、中间人』,用在ActiveMQ的构架中,即:Broker作为Producer与Consumer的中间人(或代理人),生产者不用知道消费者在哪里、如何消费这些细节,只要将消息扔给中间人Broker即可,类似的,消费者也不用关心消息是从哪个生产者过来的,它只知道这是从Broker那里拿来的,如果画一张图来描述,就是下面这样(引用自本文最后参考文章中的图片)

那么,当生产者将消息发给Broker时,会发生什么?下图描述的就是这个过程:

1) 生产者将消息发给Broker

2) Broker将消息落地存储

3) 然后给生产者反馈:事情我已经办妥了!

继续,再来看看消费者又是如何跟Broker打交道的:

1) Broker将接收到的消息,从db中取出

2) 然后发送给消费者

3) 如果消费者使用的是自动确认模式(即:Session.AUTO_ACKNOWLEDGE),则Consumer会马上告诉Broker:ok,消息我已经收到了。

4) 然后进行自己的业务处理

5) Broker一旦收到确认,将会马上更新消息的状态为已消费(或直接删除,取决于持久化的实现机制)(注:虽然图中步骤5排在步骤4之后,但是步骤4、5几乎是同时发生的)

在一些大型应用中,如果一个Broker出现性能瓶颈抗不住压力,可能会拆分成多个Broker,如下图所示:

(注:上图中箭头的方法并非数据流向,而应该理解成调用关系,即:Producer调用Broker1,Consumer调用Broker2...)

Producer将消息发送给Broker1,而Consumer从另一个Broker2接收消息,有点类似数据库读写分离的意思,这样系统的性能可以提升一定程度的提升,但是问题来了,Broker1上的消息,如何"同步"(见下面的注释)到Broker2呢,这就依赖networkConnector的配置。

注:同步这个词用在这里可能不太准确,但也找不到一个更精确的词来描述,实际上,二个broker用上述机制组建成小集群后,如果生产者连接到broker1,消费者连接到broker2,当消息发送到broker1后,broker1不会将该消息复制一份到broker2,而是等消费者从broker2上消费该消息时,这条消息才从broker1取到broker2上,相当于此时broker2是消费者,从broker1消费了一条消息,然后broker2上就有这条消息了,最终消费者才能broker2上拿到这条消息。

 <beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd"> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<value>file:${activemq.conf}/credentials.properties</value>
</property>
</bean> <broker xmlns="http://activemq.apache.org/schema/core" brokerName="activemq-1">
<networkConnectors>
<networkConnector uri="static:(tcp://127.0.0.1:61626)"/>
</networkConnectors>
<persistenceAdapter>
<kahaDB directory="${activemq.data}/kahadb"/>
</persistenceAdapter>
<transportConnectors>
<transportConnector name="openwire"
uri="tcp://0.0.0.0:61616?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
</transportConnectors>
</broker> <import resource="jetty.xml"/>
</beans>

注意:14-16行及21-22行,该Broker对外暴露6166端口,同时"连接"到6166端口(即另1个broker),最终的效果相当于,如果有producer把消息发到6166(broker1),则从另一个broker(6166端口)上也能消费这条消息。

明白这些基本原理后,在61626对应的activemq上,也做类似的配置,只不过"连接方向"正好相反,参考以下配置:

 <beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd"> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<value>file:${activemq.conf}/credentials.properties</value>
</property>
</bean> <broker xmlns="http://activemq.apache.org/schema/core" brokerName="activemq-2">
<networkConnectors>
<networkConnector uri="static:(tcp://127.0.0.1:61616)"/>
</networkConnectors>
<persistenceAdapter>
<kahaDB directory="${activemq.data}/kahadb"/>
</persistenceAdapter>
<transportConnectors>
<transportConnector name="openwire"
uri="tcp://0.0.0.0:61626?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
</transportConnectors>
</broker> <import resource="jetty.xml"/>
</beans>

(注:如果希望2个activemq上都能访问admin管理界面,jetty.xml中的端口要修改,不要冲突)

这样,activemq-1与activemq-2这二个broker就互为主备,发给你的消息会同步到我,发给我的消息也会同步到你,实现了HA,示意图如下:

Producer与Consumer连接到activemq时,配置文件可以这么写:

 <bean id="jmsFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">
<property name="connectionFactory">
<bean class="org.apache.activemq.ActiveMQConnectionFactory">
<!--broker服务的地址-->
<property name="brokerURL" value="failover:(tcp://localhost:61616,tcp://localhost:61626)"/>
...
</bean>
</property>
</bean>

这种HA方案的优点是占用的节点数更少(只需要2个节点),而且2个broker都可以响应消息的接收与发送,性能比zookeeper方案要好一些。

最后,再给一个简化配置的技巧,以上述的2节点HA方案中,二个activemq的配置文件都要加networkConnector配置,如果想减轻配置的工作量,也可以只在其中一个activemq设置,参考以下片段:

    <networkConnectors>
<networkConnector uri="static:(tcp://127.0.0.1:61626)" duplex="true"/>
</networkConnectors>

即:在61616这个activemq的配置文件中,添加networkConnector时,增加duplex="true",也就是双工通讯的意思,这样61616与61626这二个activemq上的broker就建立了双向通讯连接,另一个activemq上就无需额外配置了(注:如果在61626上配置了,反而会报错)

参考文章:

http://www.jakubkorab.net/2011/11/understanding-activemq-broker-networks.html

http://activemq.apache.org/networks-of-brokers.html

最后贴二段程序运行的输出日志,以说明同步机制的正确性,打消回复中“大鹏520”的顾虑:

背景: *.*.*.15 与 *.*.*.16 作为HA(双主)的activemq集群,发送程序只连接到15发消息,然后退出。 接收程序只从16上收消息,如果收到了,表明15上的消息同步到16。

下面是发送程序的输出片段:(注意输出日志中关于IP的部分,这是只连接到*.*.*.[b]15[/b]上发送的) (注:部分敏感信息,比如真实IP前缀,公司package名,用*代替了)

14:53:09,996 <*.DemoSender> INFO [main]: 准备发送消息...
14:53:10,270 <org.apache.activemq.transport.WireFormatNegotiator> DEBUG [main]: Sending: WireFormatInfo { version=11, properties={TcpNoDelayEnabled=true, SizePrefixDisabled=false, CacheSize=1024, StackTraceEnabled=true, CacheEnabled=true, TightEncodingEnabled=true, MaxFrameSize=9223372036854775807, Host=*.*.*.15, MaxInactivityDuration=30000, MaxInactivityDurationInitalDelay=10000}, magic=[A,c,t,i,v,e,M,Q]}
14:53:10,306 <org.apache.activemq.transport.InactivityMonitor> DEBUG [ActiveMQ Transport: tcp:///*.*.*.:61616@50616]: Using min of local: WireFormatInfo { version=11, properties={TcpNoDelayEnabled=true, SizePrefixDisabled=false, CacheSize=1024, StackTraceEnabled=true, CacheEnabled=true, TightEncodingEnabled=true, MaxFrameSize=9223372036854775807, Host=*.*.*.15, MaxInactivityDuration=30000, MaxInactivityDurationInitalDelay=10000}, magic=[A,c,t,i,v,e,M,Q]} and remote: WireFormatInfo { version=11, properties={TcpNoDelayEnabled=true, SizePrefixDisabled=false, CacheSize=1024, StackTraceEnabled=true, CacheEnabled=true, TightEncodingEnabled=true, MaxFrameSize=104857600, MaxInactivityDuration=30000, MaxInactivityDurationInitalDelay=10000}, magic=[A,c,t,i,v,e,M,Q]}
14:53:10,306 <org.apache.activemq.transport.WireFormatNegotiator> DEBUG [ActiveMQ Transport: tcp:///*.*.*.:61616@50616]: Received WireFormat: WireFormatInfo { version=11, properties={TcpNoDelayEnabled=true, SizePrefixDisabled=false, CacheSize=1024, StackTraceEnabled=true, CacheEnabled=true, TightEncodingEnabled=true, MaxFrameSize=104857600, MaxInactivityDuration=30000, MaxInactivityDurationInitalDelay=10000}, magic=[A,c,t,i,v,e,M,Q]}
14:53:10,306 <org.apache.activemq.transport.WireFormatNegotiator> DEBUG [ActiveMQ Transport: tcp:///*.*.*.:61616@50616]: tcp:///*.*.*.15:61616@50616 before negotiation: OpenWireFormat{version=11, cacheEnabled=false, stackTraceEnabled=false, tightEncodingEnabled=false, sizePrefixDisabled=false, maxFrameSize=9223372036854775807}
14:53:10,307 <org.apache.activemq.transport.WireFormatNegotiator> DEBUG [ActiveMQ Transport: tcp:///*.*.*.:61616@50616]: tcp:///*.*.*.15:61616@50616 after negotiation: OpenWireFormat{version=11, cacheEnabled=true, stackTraceEnabled=true, tightEncodingEnabled=true, sizePrefixDisabled=false, maxFrameSize=104857600}
14:53:10,416 <org.springframework.jms.core.JmsTemplate> DEBUG [main]: Executing callback on JMS Session: PooledSession { ActiveMQSession {id=ID:yangjunmings-MacBook-Pro.local-50615-1461480790088-1:1:1,started=false} java.lang.Object@5a56cdac }
14:53:10,471 <org.springframework.jms.core.JmsTemplate> DEBUG [main]: Sending created message: ActiveMQTextMessage {commandId = 0, responseRequired = false, messageId = null, originalDestination = null, originalTransactionId = null, producerId = null, destination = null, transactionId = null, expiration = 0, timestamp = 0, arrival = 0, brokerInTime = 0, brokerOutTime = 0, correlationId = null, replyTo = null, persistent = false, type = null, priority = 0, groupID = null, groupSequence = 0, targetConsumerId = null, compressed = false, userID = null, content = null, marshalledProperties = null, dataStructure = null, redeliveryCounter = 0, size = 0, properties = null, readOnlyProperties = false, readOnlyBody = false, droppable = false, jmsXGroupFirstForConsumer = false, text = message test:0}
14:53:10,516 <*.DemoSender> INFO [main]: 消息0发送完成!
14:53:11,520 <org.springframework.jms.core.JmsTemplate> DEBUG [main]: Executing callback on JMS Session: PooledSession { ActiveMQSession {id=ID:yangjunmings-MacBook-Pro.local-50615-1461480790088-1:1:1,started=false} java.lang.Object@5a56cdac }
14:53:11,521 <org.springframework.jms.core.JmsTemplate> DEBUG [main]: Sending created message: ActiveMQTextMessage {commandId = 0, responseRequired = false, messageId = null, originalDestination = null, originalTransactionId = null, producerId = null, destination = null, transactionId = null, expiration = 0, timestamp = 0, arrival = 0, brokerInTime = 0, brokerOutTime = 0, correlationId = null, replyTo = null, persistent = false, type = null, priority = 0, groupID = null, groupSequence = 0, targetConsumerId = null, compressed = false, userID = null, content = null, marshalledProperties = null, dataStructure = null, redeliveryCounter = 0, size = 0, properties = null, readOnlyProperties = false, readOnlyBody = false, droppable = false, jmsXGroupFirstForConsumer = false, text = message test:1}

运行完以后,关掉发送程序,然后启动接收程序

14:54:40,965 <org.apache.activemq.transport.WireFormatNegotiator> DEBUG [main]: Sending: WireFormatInfo { version=11, properties={TcpNoDelayEnabled=true, SizePrefixDisabled=false, CacheSize=1024, StackTraceEnabled=true, CacheEnabled=true, TightEncodingEnabled=true, MaxFrameSize=9223372036854775807, Host=*.*.*.16, MaxInactivityDuration=30000, MaxInactivityDurationInitalDelay=10000}, magic=[A,c,t,i,v,e,M,Q]}
14:54:41,002 <org.apache.activemq.transport.InactivityMonitor> DEBUG [ActiveMQ Transport: tcp:///*.*.*.16:61616@50642]: Using min of local: WireFormatInfo { version=11, properties={TcpNoDelayEnabled=true, SizePrefixDisabled=false, CacheSize=1024, StackTraceEnabled=true, CacheEnabled=true, TightEncodingEnabled=true, MaxFrameSize=9223372036854775807, Host=*.*.*.16, MaxInactivityDuration=30000, MaxInactivityDurationInitalDelay=10000}, magic=[A,c,t,i,v,e,M,Q]} and remote: WireFormatInfo { version=11, properties={TcpNoDelayEnabled=true, SizePrefixDisabled=false, CacheSize=1024, StackTraceEnabled=true, CacheEnabled=true, TightEncodingEnabled=true, MaxFrameSize=104857600, MaxInactivityDuration=30000, MaxInactivityDurationInitalDelay=10000}, magic=[A,c,t,i,v,e,M,Q]}
14:54:41,003 <org.apache.activemq.transport.WireFormatNegotiator> DEBUG [ActiveMQ Transport: tcp:///*.*.*.16:61616@50642]: Received WireFormat: WireFormatInfo { version=11, properties={TcpNoDelayEnabled=true, SizePrefixDisabled=false, CacheSize=1024, StackTraceEnabled=true, CacheEnabled=true, TightEncodingEnabled=true, MaxFrameSize=104857600, MaxInactivityDuration=30000, MaxInactivityDurationInitalDelay=10000}, magic=[A,c,t,i,v,e,M,Q]}
14:54:41,003 <org.apache.activemq.transport.WireFormatNegotiator> DEBUG [ActiveMQ Transport: tcp:///*.*.*.16:61616@50642]: tcp:///*.*.*.16:61616@50642 before negotiation: OpenWireFormat{version=11, cacheEnabled=false, stackTraceEnabled=false, tightEncodingEnabled=false, sizePrefixDisabled=false, maxFrameSize=9223372036854775807}
14:54:41,004 <org.apache.activemq.transport.WireFormatNegotiator> DEBUG [ActiveMQ Transport: tcp:///*.*.*.16:61616@50642]: tcp:///*.*.*.16:61616@50642 after negotiation: OpenWireFormat{version=11, cacheEnabled=true, stackTraceEnabled=true, tightEncodingEnabled=true, sizePrefixDisabled=false, maxFrameSize=104857600}
14:54:41,163 <org.apache.activemq.thread.TaskRunnerFactory> DEBUG [main]: Initialized TaskRunnerFactory[ActiveMQ Session Task] using ExecutorService: java.util.concurrent.ThreadPoolExecutor@485966cc[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
14:54:41,181 <*.DemoListener> INFO [ActiveMQ Session Task-1]: message test:0

注意接收程序中IP的部分,这是从*.*.*.[b]16[/b]上接收到的,说明消息已经从 *.*.*.15同步到*.*.*.16上了,否则不可能收到消息。

ActiveMQ笔记(3):基于Networks of Brokers的HA方案的更多相关文章

  1. 基于Networks of Brokers的HA方案

    上一篇介绍了基于ZK的ActiveMQ HA方案,虽然理解起来比较容易,但是有二个不足: 1)  占用的节点数过多,1个zk集群至少3个节点,1个activemq集群也至少得3个节点,但其实正常运行时 ...

  2. ActiveMQ笔记(2):基于ZooKeeper的HA方案

    activemq官网给出了3种master/slave的HA方案,详见:http://activemq.apache.org/masterslave.html,基于共享文件目录,db,zookeepe ...

  3. ActiveMQ笔记(4):搭建Broker集群(cluster)

    上一篇介绍了基于Networks of Borkers的2节点HA方案,这一篇继续来折腾Networks of Brokers,当应用规模日渐增长时,2节点的broker可能仍然抗不住访问压力,这时候 ...

  4. ActiveMQ笔记:一个高稳定,可扩展的的部署方案

    本文介绍一个笔者在实际工作中的实施的基于ActiveMQ的一个高稳定,可扩展的异步消息系统. ActiveMQ是一个成熟的基于Java语言的开源消息系统,在实际应用中被大量使用.ActiveMQ在系统 ...

  5. ActiveMQ笔记(7):如何清理无效的延时消息?

    ActiveMQ的延时消息是一个让人又爱又恨的功能,具体使用可参考上篇ActiveMQ笔记(6):消息延时投递,在很多需要消息延时投递的业务场景十分有用,但是也有一个缺陷,在一些大访问量的场景,如果瞬 ...

  6. 【DWT笔记】基于小波变换的降噪技术

    [DWT笔记]基于小波变换的降噪技术 一.前言 在现实生活和工作中,噪声无处不在,在许多领域中,如天文.医学图像和计算机视觉方面收集到的数据常常是含有噪声的.噪声可能来自获取数据的过程,也可能来自环境 ...

  7. RGMII_PHY测试笔记1 基于开发板MiS603-X25

    RGMII_PHY测试笔记1 基于开发板MiS603-X25 作者:汤金元 日期:20150817 公司:南京米联电子科技有限公司 博客:http://blog.chinaaet.com/detail ...

  8. 《软件测试自动化之道》读书笔记 之 基于反射的UI测试

    <软件测试自动化之道>读书笔记 之 基于反射的UI测试 2014-09-24 测试自动化程序的任务待测程序测试程序  启动待测程序  设置窗体的属性  获取窗体的属性  设置控件的属性  ...

  9. 《软件测试自动化之道》读书笔记 之 基于Windows的UI测试

    <软件测试自动化之道>读书笔记 之 基于Windows的UI测试 2014-09-25 测试自动化程序的任务待测程序测试程序  启动待测程序  获得待测程序主窗体的句柄  获得有名字控件的 ...

随机推荐

  1. Linux驱动开发——pr_fmt的用法

    作者:彭东林 邮箱:pengdonglin137@163.com 在阅读kernel代码的时候,总是看到有很多驱动都在第一行定义pr_fmt,闲来没事,分析了一下, 发现,确实挺方便的.下面记录分享一 ...

  2. 深入理解CSS动画animation

    × 目录 [1]定义 [2]关键帧 [3]动画属性 [4]多值 [5]API 前面的话 transition过渡是通过初始和结束两个状态之间的平滑过渡实现简单动画的:而animation则是通过关键帧 ...

  3. 重新诠释的OSGi规范

    上周五部门开会讨论新一代产品(基于.net Winform)的设计规范,从设计规范慢慢讨论到体系结构等架构存在的问题,诸如菜单.工具条.状态条.界面布局等不能实现配置化和自动化,子系统之间拥有强依赖, ...

  4. jquery手风琴

    --js $(document).ready(function(){ //Set default open/close settings$('.acc_container').hide(); //Hi ...

  5. js sort() reverse()

    数组中存在的两个方法:sort()和reverse() 直接用sort(),如下: ,,,,,,,,,,,]; console.log(array.sort());ps:[0, 1, 2, 2, 29 ...

  6. SharePoint Designer 2013 连接 Office 365 必需安装2个SP

    第一个: 32位电脑安装链接:http://www.microsoft.com/downloads/details.aspx?FamilyId=278a31eb-0cf9-4b30-a670-9c9d ...

  7. JavaScript图表FusionCharts免费在线公开课,由印度原厂技术工程师主讲,10月13日发车

    FusionCharts公开课达人还你做 轻松晋升图表大师 [开课时间]10月13日 14:30[主讲老师]印度原厂技术工程师[开课形式]网络在线公开课[活动费用]前50名免费 现在就可以报名哦  报 ...

  8. Android开发aidl使用中linkToDeath和unlinkToDeath的使用

    1.Binder死亡代理     这一节首先将介绍Binder类中比较重要的两个方法linkToDeath和unlinkToDeath.我们知道Binder是运行在服务进程,若服务端进程因为某种原因“ ...

  9. (十四)Maven聚合与继承

    1.Maven聚合 我们在平时的开发中,项目往往会被划分为好几个模块,比如common公共模块.system系统模块.log日志模块.reports统计模块.monitor监控模块等等.这时我们肯定会 ...

  10. js继承方式

    1.原型链 实现的本质是重写原型对象,代之以一个新类型的实例: 给原型添加方法的代码硬顶放在替换原型的语句之后: 不能使用对象字面量查收能见原型方法,这样会重写原型链. 缺点:包含引用类型值的原型属性 ...