ActiveMQ的集群与高可用

一、ActiveMQ的高可用性

ActiveMQ使用master-slave模式实现高可用性,提供两种实现主从模式的配置:shared nothing、shared storage(a relational database and a shared file system)

1.shared nothing master-slave

每一个broker(包括master和slave)都有自己的消息存储区,这是最简单的高可用性实现的办法。

master复制所有的消息指令给slave,复制的动作发生在master回复client消息已接收之前。

slave broker会在启动的时候去连接master,所以理想上,master broker应该先启动,slave broker 不会打开任何transports,也就是说,slave broker不接收任何client请求和网络连接,除非master挂掉。slave通过检测它与master之间的连接失败而判定master挂掉。

shared nothing master-slave模式的处理过程:当一个生产者发送一个持久化消息到master之后,master会复制该消息给slave,再返回接收应答给生产者,生产者才能发送下一个消息。

当master broker挂掉后,slave有两个选择:

1.关掉自己,因此,它只会保存master的状态。

2.打开transports并且初始化所有的network connections,因此,该slave自动成为新的master。

如果slave broker成为新的master broker,所有的client可以通过failover机制去连接上新的master。在默认的client连接中,failover传输机制皆可以连接到master和slave:

failover://(tcp://masterhost:61616,tcp://slavehost:61616)?randomize=false

不过,使用shared nothing master-slave也有限制,如果client先连上master进行工作,而slave还没与master进行连接,master挂掉,消息很可能会丢失。ActiveMQ提供了一个waitForSlave属性去设置master broker,强制master如果还没与slave建立好连接,那么不会接受任何client的连接,另一个限制是,一个master只允许有一个slave。

配置shared nothing master-slave

配置一个broker成为slave很简单,配置一个masterConnector service:

<services>

   <!--

   remoteURI:master broker的监听地址

   userName:Optional,如果master需要身份验证

   password:Optional,如果master需要身份验证

   -->

   <masterConnector remoteURI="tcp://remotehost:62001" userName="" password=""/></services>

2.shared storage master-slave

share nothing master-slave模式下,每一个broker都独自维护自己的storage,而shared storage master-slave模式允许多个broker共享存储,但同一个时刻只有一个broker是存活的。shared storage master-slave的好处在于,它确保了当master挂掉之后,无需手动干预去保持应用的完整性,另一个好处是,slave的数量不再有所限制。

share nothing master-slave模式的配置有两种:a relational database和file system-based storage.

shared database master-slave

当一个ActiveMQ broker使用关系型数据库时,它持有表的锁以确保没有其他broker同时访问这个数据库。多个broker同时运行并尝试去访问数据库时,只有第一个broker会连接成功并拿到锁,其他随后到来的broker会一直poll直到它可以获得锁为止。这些处于polling状态的broker,被视为slave,它们不会开启任何传输连接或者网络连接。

该配置中所有的broker可以使用同一份配置文件,这使得activemq启动起来简单得多。

shared file system master-slave

它建议使用 KahaDB 消息存储,但是对于消息存储使用底层的共享的文件系统。当KahaDB消息存储启动时,它将尝试获取文件锁,以防止任何其他broker同时访问基于文件的消息存储。

二、ActiveMQ是如何在brokers之间传递消息

ActiveMQ中有一个概念:networks of brokers,它指的是连接ActiveMQ的消息代理在一起形成不同的拓扑结构。

接下来就是分析brokers是如何在一个network中发现彼此且如何配置broker使其在network中合作。

1.存储和转发(store and forward)

ActiveMQ的存储和转发概念意味着,消息在通过network转发到其他broker之前,总是被存储在本地broker中,也就是说,如果一条消息由于连接原因没有被交付,比如说,正在重连,broker将能够通过网络连接将未交付的消息发送到远程broker。默认情况下,network仅以单向方式操作,如图:

当然,这并不是说network只能单向操作,如果想要双向操作,同样可以在远程broker中配置一个network connector指向本地的broker,或者直接指定创建的network connector为双向duplex。

当本地broker和远程broker之间建立好一条network后,远程broker会将其所有持久和处于活动的消费者的目的地信息传递给本地broker,本地broker使用这些信息去判断远程broker对哪种消息感兴趣,并转发该类型消息给它。

这里,书中举了一个场景,假如我们有多个超市需要连接到一个后台办公订购系统,这将很难灵活扩展新的超市,后台办公订购系统不好掌控所有新加入的超市即远程broker。

给个图示,这里想象超市和后台之间有一个防火墙。(至于为什么这么想象,我并不得知)注意到这里,超市broker和back office之间的network是双向的,超市broker的配置:

<networkConnectors>

   <networkConnector uri="static://(tcp://backoffice:61617)"

       name="bridge"

       duplex="true"

       conduitSubscriptions="true"

       decreaseNetworkConsumerPriority="false">

   </networkConnector></networkConnectors>

这里关于配置,主要注意一点是,配置的顺序是很重要的,关于networks,persistence,transports的顺序如下:

Networks——必须在消息存储之前创建

Message store——必须在传输配置好之前配置完

Transports——必须在broker配置的最后

举个例子:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://activemq.apache.org/schema/core">

   <broker brokerName="receiver" persistent="true" useJmx="true">

       <networkConnectors>

           <networkConnector uri="static:(tcp://backoffice:61617)"/>

       </networkConnectors>

       <persistenceAdapter>

           <kahaDB directory = "activemq-data"/>      

       </persistenceAdapter>

       <transportConnectors>

           <transportConnector uri="tcp://localhost:62002"/>

       </transportConnectors>

   </broker></beans>

来一张,在大型开发场景下的高可用性和network配置结合:

2.Network发现机制

ActiveMQ提供两种发现机制:

Dynamic——使用组播和会合方式搜索brokers

Static——通过一个URI列表配置brokers

使用组播发现的方式去创建network连接是最简单粗暴的,当你启动一个broker时,它会通过组播IP去搜索其他的broker并创建network连接。配置方式如下:

<networkConnectors>    <networkConnector uri="multicast://default"/></networkConnectors>

这里“default”表示这个broker属于哪个组,建议使用唯一的名字去标识,以免你的broker连接到其他你不知道的应用代理上。

组播发现机制有一些限制,比如说不能控制哪些broker被发现,事实上,它通常局限于本地网段上去发现其他broker,因为组播IP不通过路由器延伸。

关于第二种方式,static,事实上,在这之前的配置一直都是static,只不过broker的URL列表有点少而已,

<networkConnectors>    <networkConnector uri="static:(tcp://remote-master:61617,tcp://remote-slave:61617)"/></networkConnectors>

static的配置属性:

3.Network配置

对于远程broker现存在的目的地,可能没有任何活动持久的订阅者或消费者,因此,当network初始化连接到远程broker时,远程broker会读取它现存目的地的消息,并传递给本地broker,然后,本地broker也可以转发那些目的地的消息。

重要的是要注意,一个network将使用broker的名称来代表远程broker创建唯一的持久预订代理。 因此,如果在稍后的时间点更改broker的名称,很可能会通过network丢失持久主题订阅者的消息。 为避免这种情况,请确保为元素上的brokerName属性使用唯一的名称。 有关简要示例,请参阅以下内容:

<broker xmlns="http://activemq.apache.org/schema/core/"

   brokerName="brokerA"

   dataDirectory="${activemq.base}/data">...    <networkConnectors>

       <networkConnector name="brokerA to brokerB" uri="tcp://remotehost:61616"/>

   </networkConnectors></broker>

关于Network配置还有很多,不一一列举了。

三、为大量并发应用程序部署ActiveMQ

扩展使用ActiveMQ的应用程序可能需要一些时间,需要一些努力。 在本节中,将介绍三种技术来帮助完成此任务。首先是垂直扩展,单个broker用于数千个连接和队列。然后将通过使用network水平扩展应用程序来扩展到数万个连接。 最后,将研究流量分区,这将平衡扩展和性能,但会增加ActiveMQ应用程序的复杂性。

1.垂直扩展

垂直扩展是一种用于增加单个ActiveMQ broker可以处理的连接数(因此增加负载)的技术。默认情况下,ActiveMQ broker设计为尽可能高效地移动消息,以确保低延迟和良好的性能。但是我们可以做一些配置调整,以确保ActiveMQ broker可以处理大量的并发连接和大量的队列。

默认情况下,ActiveMQ将使用阻塞I/O来处理传输连接。 这导致每个连接使用一个线程。 我们可以在ActiveMQ broker上使用非阻塞I/O(而客户端上仍然使用默认传输)来减少使用的线程数。broker的非阻塞I/O配置如下:

<broker>    <transportConnectors>

       <transportConnector name="nio" uri="nio://localhost:61616"/>

   </transportConnectors></broker>

除了每个连接使用一个线程来阻塞I/O外,ActiveMQ broker可以使用线程为每个客户端连接分派消息。可以通过将名为org.apache.activemq.UseDedicatedTaskRunner的系统属性设置为false,让ActiveMQ使用线程池。

ACTIVEMQ_OPTS="-Dorg.apache.activemq.UseDedicatedTaskRunner=false"

1

确保ActiveMQ broker具有足够的内存来处理大量并发连接有两步过程。

首先,需要确保启动ActiveMQ broker的JVM配置了足够的内存。

ACTIVEMQ_OPTS="-Xmx1024M -Dorg.apache.activemq.UseDedicatedTaskRunner=false"

1

第二,确保专门为ActiveMQ broker在JVM配置适当的内存量。此调整通过< system-Usage >元素的limit属性进行。(最好从512MB开始,如果测试不够再往上加),配置示例:

<systemUsage>

   <systemUsage>

       <memoryUsage>

           <memoryUsage limit="512 mb"/>

       </memoryUsage>

       <storeUsage>

           <storeUsage limit="10 gb" name="foo"/>

       </storeUsage>

       <tempUsage>

           <tempUsage limit="1 gb"/>

       </tempUsage>

   </systemUsage></systemUsage>

还应该降低每一个连接的CPU负载,如果使用的OpenWire连接方式,禁用紧密编码,否则会使得CPU过度紧张。

String uri = "failover://(tcp://localhost:61616?" + " wireFormat.tightEncodingEnabled=false)";

ConnectionFactory cf = new ActiveMQConnectionFactory(uri);

前面研究的是broker怎么调整去处理数千个连接,下面开始研究的是怎么调整broker去处理数千个队列。

默认队列配置使用单独的线程来将消息从消息存储区分页到队列中,以便分发给感兴趣的消息消费者。 对于大量队列,建议通过为所有队列启用optimize-Dispatch属性来禁用此功能,

<destinationPolicy>    <policyMap>

       <policyEntries>

           <policyEntry queue=">" optimizedDispatch="true"/>

       </policyEntries>

   </policyMap></destinationPolicy>

为了确保不仅可以扩展到数千个连接,而且还可以扩展到数万个队列,使用JDBC消息存储库或更新和更快的KahaDB消息存储库。 KahaDB默认情况下在ActiveMQ中启用。

到目前为止,我们已经考虑了扩展连接,减少线程使用,并选择正确的消息存储。 调整用于扩展的ActiveMQ的示例配置如以下:

<broker xmlns="http://activemq.apache.org/schema/core" brokerName="amq-broker" dataDirectory="${activemq.base}/data">

   <persistenceAdapter>

       <kahaDB directory="${activemq.base}/data" journalMaxFileLength="32mb"/>

   </persistenceAdapter>

   <destinationPolicy>

       <policyMap>

           <policyEntries>

               <policyEntry queue=">" optimizedDispatch="true"/>

           </policyEntries>

       </policyMap>

   </destinationPolicy>

   <systemUsage>

       <systemUsage>

           <memoryUsage>

               <memoryUsage limit="512 mb"/>

           </memoryUsage>

           <storeUsage>

               <storeUsage limit="10 gb" name="foo"/>

           </storeUsage>

           <tempUsage>

               <tempUsage limit="1 gb"/>

           </tempUsage>

       </systemUsage>

   </systemUsage>

   <transportConnectors>

       <transportConnector name="openwire" uri="nio://localhost:61616"/>

   </transportConnectors></broker>

2.水平扩展

除了扩展单个broker之外,还可以使用networks来增加可用于应用程序的ActiveMQ broker的数量。 由于networks会自动将消息传递给具有感兴趣的消费者的连接broker,因此可以将客户端配置为连接到一个broker集群,随机选择一个来连接。

failover://(tcp://broker1:61616,tcp://broker2:61616)?randomize=true

为了确保队列或持久主题订阅者的消息不会在broker上孤立,需要将network配置为使用dynamicOnly和低网络prefetchSize。

<networkConnector uri="static://(tcp://remotehost:61617)"

   name="bridge"

   dynamicOnly="true"

   prefetchSize="1"></networkConnector>

使用network进行水平扩展会带来更多的延迟,因为潜在的消息必须在分发给消费者之前通过多个broker。

另一种替代部署提供了巨大的可扩展性和性能,但需要更多的应用规划。 这种混合解决方案称为流量分区。

3.流量分区

客户端流量分割是垂直和水平分割的混合。 通常不使用network,因为客户端应用程序决定什么流量应该到哪个broker上。 客户端应用程序必须维护多个JMS连接,并决定哪些JMS连接应用于哪些目标。

不直接使用network connection的优点是,减少在brokers之间转发消息的开销。 需要平衡这与导致典型应用程序的额外复杂性。Fig10.8是流量分区的一个使用代表.

欢迎学Java和大数据的朋友们加入java架构交流: 855835163

加群链接:https://jq.qq.com/?_wv=1027&amp;k=5dPqXGI

群内提供免费的架构资料还有:Java工程化、高性能及分布式、高性能、深入浅出。高架构。性能调优、Spring,MyBatis,Netty源码分析和大数据等多个知识点高级进阶干货的免费直播讲解  可以进来一起学习交流哦

推荐阅读更多精彩内容