鱼与熊掌兼得法-完美解决方案
我们知道:
- master/slave模式下,消息会被逐个复制
- 而cluster模式下,请求会被自动派发
那么可不可以把两者集成起来呢?答案是有的,网上所谓的独创。。。统统是错的!!对,因为我全试验过了我敢这么説,写得都是些个啥呀。。。一个个还COPY不走样,全错了而且。
我这个才叫独创,来看原理。
MASTER SLAVE+BROKER CLUSTER的搭建
- 我们使用ZK搭建MASTER SLAVE
- 我们使用BROKER CLUSTER把两个“组”合并在一起
先来看下面的集群规划
MASTER SLAVE+BROKER CLUSTER的搭建-Group1的配置
由于这涉及到两个组6个ActiveMQ的实例配置,如果把6个配置全写出来是完全没有必要的,因此我就把配置分成两组来写吧。每个组的配置对于其组内各个节点都为一致的,除去那些个端口号。Group1的配置(保持6个实例中brokerName全部为一致)
[html]
view plain
copy
print
?
- <networkConnectors>
- <networkConnector uri="static:(tcp://ymklinux:61619,tcp://ymklinux:61620,tcp://ymklinux:61621)" duplex=“false"/>
- </networkConnectors>
<networkConnectors>
<networkConnector uri="static:(tcp://ymklinux:61619,tcp://ymklinux:61620,tcp://ymklinux:61621)" duplex=“false"/>
</networkConnectors>
可以看到这边的broker cluster的配置是用来确保每一台都可以和Group2中的各个节点保持同步
[html]
view plain
copy
print
?
- <persistenceAdapter>
- <replicatedLevelDB
- directory="${activemq.data}/leveldb"
- replicas="3"
- bind="tcp://0.0.0.0:0"
- zkAddress="192.168.0.101:2181"
- zkPassword=""
- hostname="ymklinux"
- sync="local_disk"
- zkPath="/activemq/leveldb-stores/group1"
- />
- </persistenceAdapter>
<persistenceAdapter>
<replicatedLevelDB
directory="${activemq.data}/leveldb"
replicas="3"
bind="tcp://0.0.0.0:0"
zkAddress="192.168.0.101:2181"
zkPassword=""
hostname="ymklinux"
sync="local_disk"
zkPath="/activemq/leveldb-stores/group1"
/>
</persistenceAdapter>
MASTER SLAVE+BROKER CLUSTER的搭建-Group2的配置
[html]
view plain
copy
print
?
- <networkConnectors>
- <networkConnector uri="static:(tcp://ymklinux:61616,tcp://ymklinux:61617,tcp://ymklinux:61618)" duplex=“false"/>
- </networkConnectors>
<networkConnectors>
<networkConnector uri="static:(tcp://ymklinux:61616,tcp://ymklinux:61617,tcp://ymklinux:61618)" duplex=“false"/>
</networkConnectors>
可以看到这边的broker cluster的配置是用来确保每一台都可以和Group1中的各个节点保持同步
[html]
view plain
copy
print
?
- <persistenceAdapter>
- <replicatedLevelDB
- directory="${activemq.data}/leveldb"
- replicas="3"
- bind="tcp://0.0.0.0:0"
- zkAddress="192.168.0.101:2182"
- zkPassword=""
- hostname="ymklinux"
- sync="local_disk"
- zkPath="/activemq/leveldb-stores/group2"
- />
- </persistenceAdapter>
<persistenceAdapter>
<replicatedLevelDB
directory="${activemq.data}/leveldb"
replicas="3"
bind="tcp://0.0.0.0:0"
zkAddress="192.168.0.101:2182"
zkPassword=""
hostname="ymklinux"
sync="local_disk"
zkPath="/activemq/leveldb-stores/group2"
/>
</persistenceAdapter>
MASTER SLAVE+BROKER CLUSTER的搭建-客户端
客户端:
[html]
view plain
copy
print
?
- <property name="brokerURL" value="failover:(tcp://192.168.0.101:61616,
- tcp://192.168.0.101:61617,
- tcp://192.168.0.101:61618,
- tcp://192.168.0.101:61619,
- tcp://192.168.0.101:61620,
- tcp://192.168.0.101:61621)" />
<property name="brokerURL" value="failover:(tcp://192.168.0.101:61616,
tcp://192.168.0.101:61617,
tcp://192.168.0.101:61618,
tcp://192.168.0.101:61619,
tcp://192.168.0.101:61620,
tcp://192.168.0.101:61621)" />
把6台实例全部启动起来(乖乖,好家伙)
把客户端写上全部6台实例(乖乖,好长一陀)
MASTER SLAVE+BROKER CLUSTER的搭建-实验
- 生产端连上了61616,发送了3条消息,然后我们把61616所属的activemq1的进程直接杀了
- 然后运行消费端,消费端连上了61618,消费成功3条消息
搞定了!
哈 哈 ,不要急,再重复一遍该实验,来:
- 先把所有实例进程全部杀掉
- 把6台实例全部启动起来(乖乖,好家伙)
- 把客户端写上全部6台实例(乖乖,好长一陀)
- 使用生产端任意发送3条消息。
- 生产端连上了61616,发送了3条消息,然后我们把61616所属的activemq1的进程直接杀了
- 然后运行消费端,消费端连上了61620,控台显示无任何消息消费
哈 哈 ,死惨了。。。作者滚粗。。。
为什么 ?WHY?
所谓的完美方案并不是真正的完美方案-情况A
是的,以上的方案存在着这一漏洞!来看原理!
对于上述情况,当61616宕机后,如果此时请求被failover到了group1中任意一个节点时,此时消费端完全可以拿到因为宕机而未被消费掉的消费。
所谓的完美方案并不是真正的完美方案-情况B
对于上述情况,当61616宕机后,如果此时请求被failover到了group2中任意一个节点时,此时因为group2中并未保存group1中的任何消息,因此只要你的请求被转发到另一个group中,消费端是决无可能去消费本不存在的消息的。这不是有可能而是一定会发生的情况。因为在生产环境中,请求是会被自由随机的派发给不同的节点的。
我为什么要提这个缺点、要否认之前的篇章? 我就是为了让大家感受一下网上COPY复制还是错误信息的博文的严重性,那。。。怎么做是真正完美的方案呢。。。
如何让方案完美
我们来看,如果我们把两个group间可以打通,是不是就可以在上述情况发生时做到failover到group2上时也能消费掉group1中的消息了呢?
怎么做?很简单,其实就是一个参数
duplex=true
什么叫duplex=true?
它就是用于mq节点间双向传输用的一个功能,看下面的例子
这就是duplex的使用方法,我很奇怪,竟然这么多人COPY 那错的1-2篇博文,但是就是没有提这个duplex的值,而且都设的是false。
duplex的作用就是mq节点1收到消息后会变成一个sender把消息发给节点2。
此时客户如果连的是节点2,也可以消息节点1中的消息,因此我给它起了一个比英译中更有现实意义的名称我管它叫“穿透”。
下面来看用“穿透机制”改造的真正完美方案吧。
最终完美解决方案
考虑到消费端可能会发生:
当Group1中有未消费的数据时时,消费端此时被转派到了Group2中的任意一个节点。
因此,在配置时需要进行如下的穿透:
按照这个思路我们在各Group中的各节点中作如下配置即可:
Group1中的配置
[html]
view plain
copy
print
?
- <networkConnectors>
- <networkConnector uri="static:(tcp://ymklinux:61619,tcp://ymklinux:61620,tcp://ymklinux:61621)" duplex="true"/>
- </networkConnectors>
<networkConnectors>
<networkConnector uri="static:(tcp://ymklinux:61619,tcp://ymklinux:61620,tcp://ymklinux:61621)" duplex="true"/>
</networkConnectors>
Group2中的配置
[html]
view plain
copy
print
?
- <networkConnectors>
- <networkConnector uri="static:(tcp://ymklinux:61616,tcp://ymklinux:61617,tcp://ymklinux:61618)" duplex="true"/>
- </networkConnectors>
<networkConnectors>
<networkConnector uri="static:(tcp://ymklinux:61616,tcp://ymklinux:61617,tcp://ymklinux:61618)" duplex="true"/>
</networkConnectors>
因为,你设的是duplex=true,它相当于下面几行的效果:
[html]
view plain
copy
print
?
- <!-- 在group1中 -->
- <networkConnector name="group1-broker1" uri="static:(tcp://broker1:61619)" duplex="false" />
-
- <!-- 在group1中 -->
- <networkConnector name="group1-broker2" uri="static:(tcp://broker0:61620)" duplex="false" />
-
- <!-- 在group1中 -->
- <networkConnector name="group1-broker3" uri="static:(tcp://broker0:61621)" duplex="false" />
-
-
- <!-- 在group2中 -->
- <networkConnector name="group2-broker1" uri="static:(tcp://broker1:61616)" duplex="false" />
-
- <!-- 在group2中 -->
- <networkConnector name="group2-broker2" uri="static:(tcp://broker0:61617)" duplex="false" />
-
- <!-- 在group2中 -->
- <networkConnector name="group2-broker3" uri="static:(tcp://broker0:61618)" duplex="false" />
<!-- 在group1中 -->
<networkConnector name="group1-broker1" uri="static:(tcp://broker1:61619)" duplex="false" />
<!-- 在group1中 -->
<networkConnector name="group1-broker2" uri="static:(tcp://broker0:61620)" duplex="false" />
<!-- 在group1中 -->
<networkConnector name="group1-broker3" uri="static:(tcp://broker0:61621)" duplex="false" />
<!-- 在group2中 -->
<networkConnector name="group2-broker1" uri="static:(tcp://broker1:61616)" duplex="false" />
<!-- 在group2中 -->
<networkConnector name="group2-broker2" uri="static:(tcp://broker0:61617)" duplex="false" />
<!-- 在group2中 -->
<networkConnector name="group2-broker3" uri="static:(tcp://broker0:61618)" duplex="false" />
现在知道为什么要duplex=true了吧!
验证
- 先把所有实例进程全部杀掉
- 把6台实例全部启动起来(乖乖,好家伙)
- 把客户端写上全部6台实例(乖乖,好长一陀)
- 使用生产端任意发送3条消息。
- 生产端连上了61619(属于Group2),发送了3条消息,然后我们把61619所属的activemq4的进程直接杀了
- 然后运行消费端,消费端连上了61616,控台显示消费成功3条消息
大功告成!!!
结束语
从生产环境的高可用性来説,如果需要使用完美解决方案的话我们至少需要以下这些实体机
2台实体机
- 每台虚出3个子节点来(用VM),供3个MQ实例运行使用,2*3共为6个
- 并且一台实体机必须承载一个GROUP,同一个GROUP最好不要跨实体机或虚拟
2台实体机
- 每台虚出3个子节点来(用VM),供3个ZK 节点使用,2*3共为6个
- 并且一台实体机必须承载一个ZK GROUP,同一个ZK GROUP最好不要跨实体机或虚拟
同一台实体机上不得又安装ZK又安装MQ
上述是ActiveMQ Master Slave + Broker Cluster的最小化配置,为了得到更高的高可用性,建议6个MQ实例间全部需要有物理机承载。
但是,上述情况是用于应对
百万级并发消息的生产环境而言才需要如此大动干戈,对于常规环境笔者建议:
搞两台高配置的VM分散在两台不同的实体机,做MASTER SLAVE即可,当然笔者强烈建议使用ZK做ActiveMQ的Master Slave,ZK可以用3个节点,分别来自3个不同的虚机(不是指在一个VM里启3个不同端口的ZK实例,而是来自于3台虚机),至于3台虚机是否在不同实体机上,这倒不是太大要求,因为在MASTER SLAVE中的ZK只作节点信息、消息同步使用,不会受太多并发访问之苦。
===activemq--MASTER SLAVE+BROKER CLUSTER 实践(三)=====
还有一种集群方式留给大家自己去练习,很简单,就是几个节点间无MASTER SLAVE也只做static Broker Cluster, 用的就是这个“穿透原理”。
比如说,我有5个节点,其中生产连着A和B,消费连着C、D、E,然后在A上对C、D、E做穿透,B也对C、D、E做穿透,客户端只对A和B做failover,这就构成了一个spoker机制。它也可以制作出一个MASTER SLAVE + Broker Cluster的模型来,然后producer对a,b进行监听,而consumer failerover至c,d,e即可,这个架构不难,动一下手就会了。