我们在前面的文章中提到了activemq的主从集群的实现,但是这里有个问题就是,如果数据量太大,一个master节点是无法快速进行处理的,而且容易导致内存溢出的情况,因此这里activemq就提供了分布式集群的实现方式,它有3种实现方式如下:
1.使用static协议的方式配置每台master节点的url地址:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://activemq.org/config/1.0">
<broker brokerName="receiver" persistent="false" useJmx="false">
<networkConnectors>
<networkConnector uri="static:(tcp://192.168.1.25:62001,tcp://192.168.12.25:62001)"/>
</networkConnectors>
<persistenceAdapter>
<memoryPersistenceAdapter/>
</persistenceAdapter>
<transportConnectors>
<transportConnector name="openwire" uri="tcp://192.168.8.25:62001"/>
</transportConnectors>
</broker>
</beans>
在static的配置中,不用配当前机器的broker节点端口,这种配置方式的缺点就是没有办法实现高可用,如果一个broker宕机后,其上的消息就可能丢失,而且会缩容集群。它的架构图如下:
2.为了避免static协议无法高可用的情况,可以采用masterslave协议来实现高可用的主从分布式集群,如下:
<persistenceAdapter>
<kahaDB directory="D:\software\apache-activemq-5.15.10 - 7\data\masterslave"/>
</persistenceAdapter>
<transportConnectors>
<transportConnector name="openwire" uri="tcp://192.168.8.25:62001?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
</transportConnectors>
<networkConnectors>
<networkConnector name="localhost6" uri="masterslave:(tcp://192.168.1.25:62001,tcp://192.168.1.25:62002,tcp://192.168.1.25:62003)"/>
<networkConnector name="localhost5" uri="masterslave:(tcp://192.168.12.25:62001,tcp://192.168.12.25:62002,tcp://192.168.12.25:62003)"/>
</networkConnectors>
在上面配置中,一定要注意主从集群的配置要实现才能实现高可用,其实现方式可以参考前面一篇文章https://blog.csdn.net/XinhuaShuDiao/article/details/103785510,并且在networkConnector中要指定name属性,否则会出现错误。它的架构图如下:
在上面2种配置中,uri地址后面可以跟上如下参数
参数 | 默认值 | 描述 |
---|---|---|
initialReconnectDelay | 1000 | 单位ms,如果useExponentialBackOff为fasle,用于重新连接的初始化等待时长 |
maxReconnectDelay | 30000 | 单位ms,用于重新连接的最大等待时长 |
useExponentialBackOff | true | 为重新连接序列中的每个失败增加重新连接之间的时间 |
backOffMultiplier | 2 | 使用指数回退时乘法器用于增加等待时间 |
3.实现default的方式实现
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://activemq.org/config/1.0">
<broker name="sender" persistent="false" useJmx="false">
<networkConnectors>
<networkConnector uri="multicast://default"/>
</networkConnectors>
<persistenceAdapter>
<memoryPersistenceAdapter/>
</persistenceAdapter>
<transportConnectors>
<transportConnector uri="tcp://localhost:0" discoveryUri="multicast://default"/>
</transportConnectors>
</broker>
</beans>
所有broker开启multicast:
<transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600" discoveryUri="multicast://default">
<publishedAddressPolicy>
<!--并添加publishedAddressPolicy 发布地址时发布IP,防止主机名不可访问的情况 -->
<publishedAddressPolicy publishedHostStrategy="IPADDRESS" />
</publishedAddressPolicy>
</transportConnector>
这种配置方式只适合于局域网中使用,因此意义有限,不适合异地网络分布式部署。
为了避免网络中不同broker节点的连接速度不一样而导致的异常情况,我们可以在broker配置属性上新增networkConnectorStartAsync=”true”的配置,来实现异步多线程的连接方式。
接下来看下NetworkConnector参数的属性:
属性 | 默认值 | 描述 |
---|---|---|
name | bridge | 网络的名称,对于相同两个broker节点之间有多个网络连接器时使用不同的名称。实际使用的时候就是如果有多个NetworkConnector,那么就要配置不同的name值。 |
dynamicOnly | false | 如果为true,则仅在相应的持久订阅重新激活时激活网络化持久订阅,默认情况下,它们在启动时激活。 |
decreaseNetworkConsumerPriority | false | 如果为true,则优先级从-5开始,随着离生产者越远(在网络跳转中)将分配给网络队列使用者的优先级降低。当为false时,所有网络消费者使用与本地消费者相同的默认优先级(0)。 |
networkTTL | 1 | 消息和订阅可以在网络中通过broker节点的数量(可以单独设置messageTTL&consumerTTL)。 |
messageTTL | 1 | 从5.9版本开始,消息可以在网络中通过的broker节点数量。 |
consumerTTL | 1 | 从5.9版本开始,订阅可以在网络中通过的broker节点数量,在一个网格中可以保持为1。 |
conduitSubscriptions | true | 订阅同一目的地的多个消费者在同一个broker节点上时被网络视为一个消费者。 |
excludedDestinations | empty | 匹配此列表的目的地不会在网络中被转发(这只适用于dynamicallyIncludedDestinations一起使用)。 |
dynamicallyIncludedDestinations | empty | 与此列表相匹配的目的地将通过网络被转发。空列表意味着不在此列表中的所有目的地都将被转发。 |
useVirtualDestSubs | false | 如果为true,则网络连接将监听虚拟目的地消费者的 advisory messages。 |
staticallyIncludedDestinations | empty | 匹配的目的地将始终通过网络传递——即使没有消费者来订阅。 |
duplex | false | 如果为true,则网络连接将同时用于produce和Consume消息。这是有用的特别是当在hub和spoke场景中使用并且hub是在防火墙一侧。 |
prefetchSize | 1000 | 设置网络连接器消费者的预取大小。它必须是>0,因为网络消费者不会轮询消息。 |
suppressDuplicateQueueSubscriptions | false | 从5.3版本开始,如果为true,则将禁止网络中介体在网络中产生的重复订阅。例如有A、B、C3个broker节点,他们的连接方式是multicast ,A消费者在broker A上,然后它会成为一个网络消费者连接到broker B和C上,另外C也会网络连接到B基于从broker A过来的消费者,B同样也会网络连接到C,当设置为true的时候,网络桥接在B和C之间将会被禁止当它们都是broker A的网络连接订阅副本。当生产者或消费者跨网络迁移,消除了死路由(阻塞消息)的可能性时,以这种方式减少路由选择就提供了确定性。networkTTL需要匹配或超过broker节点数量才能要求进行此干预。 |
bridgeTempDestinations | true | 默认值为true,是否为在broker节点网络中创建的临时目的地广播advisory messages。临时目的地通常是为请求-应答消息创建的。缺省情况下,将打开关于临时目的地的广播信息,以便请求-应答消息的消费者可以连接到网络中的另一个broker节点,并且仍然在JMSReplyTo报头中指定的临时目的地上发回应答。在大多数/所有消息都使用请求-应答模式的应用程序场景中,这将在broker节点网络上生成额外的流量,因为每个消息通常都设置一个惟一的JMSReplyTo地址(这将导致创建一个新的临时目的地并进行广播)。禁用此功能时,可以减少此类网络流量,但随后需要将请求-应答消息的生产者和消费者连接到同一broker节点。远程消费者(即通过网络中的另一个broker节点连接)将无法发送回复消息,而是引发“临时目的地不存在”异常。 |
alwaysSyncSend | false | 从5.6版本开始,如果为true,则使用请求/应答代替单向方式将非持久性消息发送到远程broker节点。该参数设置对持久消息和非持久消息是一样生效的。 |
staticBridge | false | 从5.6版本开始,如果设置为true,则broker节点将不会动态响应新的消费者。它将只使用staticallyincludeddestination属性来创建按需订阅。 |
userName | null | 要根据远程broker节点进行身份验证的用户名。 |
password | null | 要针对远程broker节点进行身份验证的用户名的密码。 |
在分布式消息网络中是不保证消息的顺序性的,这是因为网络的延迟性以及在不同的机器上性能可能也是不一样的,因此获取消息的速度也不一样,因此在顺序上是无法进行保证的,这个就需要进行业务上面的控制消息的排序来保证结果的一致性。接下来我们看下一些属性的具体使用说明。
关于conduitSubscriptions参数的使用,假如有A和B两个broker节点,他们通过bridge方式连接在一起,broker A上面有1个消费者,broker B上有2个消费者,每个消费者具有相同的priority,它们都订阅一个queue为Q.test的队列,假如生产者发送30条消息给broker A在负载均衡的情况下,当使用默认情况conduitSubscriptions=true时,它会把broker B上的2个消费者看做一个消费者,因此broker A和broker B的消费者会分别获取到15条消息,但是当把conduitSubscriptions设置为false的时候,它就会发送10条消息给broker A以及20条消息给broker B,因为根据负载均衡的原理,它会有3个消费者,每一个消费者都会消费相同的消息,因此就会出现上面这种情况了。
由于Conduit subscriptions将会忽略掉本地消费者的selector,因此它会直接将消息发送给一个远程的broker,也因此当生产的broker产生的queue消息发送给有消费者的broker节点的时候,而该消费者又具有不同的selector,因此这种消息是得不到消费,为了避免这种情况,需要设置conduitSubscriptions=false。
关于duplex参数的使用,假如有A和B两个broker节点,当设置duplex为true时,A到B上的双工网桥与A到B上的默认网桥和B到A上的默认网桥相同。如果想在两个broker节点之间配置多个双工网桥,以增加吞吐量或分区主题和队列,则必须为每个broker提供唯一的名称:
<networkConnectors>
<networkConnector name="SYSTEM1" duplex="true" uri="static:(tcp://10.x.x.x:61616)">
<dynamicallyIncludedDestinations>
<topic physicalName="outgoing.System1" />
</dynamicallyIncludedDestinations>
</networkConnector>
<networkConnector name="SYSTEM2" duplex="true" uri="static:(tcp://10.x.x.x:61616)">
<dynamicallyIncludedDestinations>
<topic physicalName="outgoing.System2"/>
</dynamicallyIncludedDestinations>
</networkConnector>
</networkConnectors>
如果需要在远程节点上面有消费者的时候才进行消息发送,并且是发送到明确的目标broker节点上,那么可以通过动态属性dynamicallyIncludedDestinations来设置,如下:
<networkConnector uri="static:(tcp://host)" destinationFilter="Queue.include.test.foo,ActiveMQ.Advisory.Consumer.Topic.include.test.bar">
<dynamicallyIncludedDestinations>
<queue physicalName="include.test.foo"/>
<topic physicalName="include.test.bar"/>
</dynamicallyIncludedDestinations>
</networkConnector>
该配置也是为了避免Advisory消息在大型的网络拓扑结构中消耗过多的网络流量,如果是在小型的网络或者为数不多的destinations和consumers中时到时影响不大,因为所有的消费者都会主动订阅 ActiveMQ.Advisory.Consumer.>这个形式的所有topic。同时这个配置也是针对5.6版本以前的配置,如果在5.6版本以后就不需要在networkConnector上面配置参数destinationFilter,因为5.6版本及以后的版本都是默认提供了的,当然destinationFilter的第一个参数没有添加ActiveMQ.Advisory.Consumer.是因为它默认提供了ActiveMQ.Advisory.Consumer.的前缀,这个也同时需要注意的是在broker属性上不能禁用advisorySupport的原因,因为消费者如果不能订阅到ActiveMQ.Advisory.Consumer.>信息的时候会报错。
如果local broker节点不想与远程broker节点上的消费者的变化与否有过多接触,我们可以使用参数staticBridge进行如下配置:
<networkConnector uri="static:(tcp://host)" staticBridge="true">
<staticallyIncludedDestinations>
<queue physicalName="always.include.queue"/>
</staticallyIncludedDestinations>
</networkConnector>
该staticBridge配置是在5.6版本及以后才生效的,同时我们需要通过属性staticallyIncludedDestinations来配置需要订阅的queue或者topic,通过配置staticBridge属性,将不会在订阅Advisory消息,因此也就是上面说的远程broker节点上的消费者的变化与否与local broker无关了,在5.6版本以前我们可以通过下面配置:
<networkConnector uri="static:(tcp://host)" destinationFilter="NO_DESTINATION">
<staticallyIncludedDestinations>
<queue physicalName="always.include.queue"/>
</staticallyIncludedDestinations>
</networkConnector>
接下来看下虚拟目标消费者和复合目标的使用,假如有2个broker节点如下:
本地broker:
<networkConnector uri="static:(tcp://host)">
<dynamicallyIncludedDestinations>
<topic physicalName="include.bar"/>
</dynamicallyIncludedDestinations>
</networkConnector>
远程broker:
<compositeTopic name="include.bar" forwardOnly="false">
<forwardTo>
<queue physicalName="include.bar.forward" />
</forwardTo>
</compositeTopic >
在远程节点中复合topic为include.bar的消息将会被转发给queue为include.bar.forward的队列,如果生产者将topic include.bar消息推送给local broker,那么在远程broker上的消费者是无法获取到消息的,因为本地broker并没有动态添加目标为include.bar.forward的queue,如果远程broker的消费者想要消费消息,则可以通过下面的配置:
远程broker,在broker属性上添加useVirtualDestSubs为true:
<beans xmlns="http://activemq.org/config/1.0">
<broker name="remoteBroker" useVirtualDestSubs="true">
.....
</broker>
</beans>
本地broker,在networkConnector属性上添加useVirtualDestSubs为true:
<networkConnector uri="static:(tcp://host)" useVirtualDestSubs="true">
<dynamicallyIncludedDestinations>
<topic physicalName="include.bar"/>
</dynamicallyIncludedDestinations>
</networkConnector>
通过上面的配置也并不能确保发送给本地broker的消息会转发给远程broker,如果并没有消费者订阅include.bar.forward消息的时候,如果要确保转发,那么可以有2中方法来实现:
1.有1个或多个queue。
2.具有持久订阅的topic。
我们还可以通过在broker属性上设置useVirtualDestSubsOnCreation参数来进行虚拟消费:
<broker name="remoteBroker" useVirtualDestSubs="true" useVirtualDestSubsOnCreation="true">
.....
</broker>
这种方式的好处就是在远程broker节点没有消费者时,仍然可以向它发送消息,因为它模拟了消费者进行消息消费。
注意的是目前excludedDestinations属性并不影响staticallyIncludedDestinations,通配符的使用只能在excludedDestinations和dynamicallyIncludedDestinations属性中,如下:
<networkConnectors>
<networkConnector uri="static:(tcp://localhost:61617)"
name="queues_only"
conduitSubscriptions="false"
decreaseNetworkConsumerPriority="false">
<excludedDestinations>
<topic physicalName=">"/>
</excludedDestinations>
</networkConnector>
</networkConnectors>
在跨网络使用持久topic订阅服务器,则不要轻易更改bridge名称或broker的名称,这是因为在内部,ActiveMQ使用网络名称和代理名称为网络构建唯一但可重复的持久订阅用户的名称。
从5.6版本开始,默认情况下,不允许将消息重播到消息来源的broker节点。这确保在配置双工或定向网络连接器时消息不会循环。但是有时我们则需要进行消息循环,如生产者和消费者可以随机选择使用故障转移传输的broker节点。如果为了维护而重新启动了一个broker,则在该broker上积累的、通过网桥的消息在消费者重新连接到代理之前将不可用。这个问题的一个解决方案是使用rebalanceClusterClients强制客户机重新连接。另一种方法是允许将消息重放回原点,因为该代理上没有本地消费者。后面一种的配置方案如下:
<destinationPolicy>
<policyMap>
<policyEntries>
<policyEntry queue="TEST.>" enableAudit="false">
<networkBridgeFilterFactory>
<conditionalNetworkBridgeFilterFactory replayWhenNoConsumers="true"/>
</networkBridgeFilterFactory>
</policyEntry>
</policyEntries>
</policyMap>
</destinationPolicy>
它是在conditionalNetworkBridgeFilterFactory属性上设置replayWhenNoConsumers为true来实现的。需要注意的是在5.9版本以下是需要配合设置enableAudit=false来禁用游标重复检测,因为游标可以将重播的消息标记为重复消息(取决于在网桥上播放和重播这些消息之间的时间窗口)。同时conditionalNetworkBridgeFilterFactory工厂允许为目的地指定速率限制,以便可以对网络使用者进行节流。网络使用者的预取在很大程度上是无效的,因为网络使用者传递消息的速度通常非常快,所以即使预取较低且优先级降低,网络使用者也会饿死速度一般快的本地使用者。节流是一种补救方法。
更多的信息可以参考官网链接https://activemq.apache.org/networks-of-brokers。