内嵌代理所引发的问题:
Pure master slave的工作方式:
当master broker失效的时候。Slave broker 做出了两种不同的相应方式
failover://(tcp://masterhost:61616,tcp://slavehost:61616)?randomize=false
<broker masterConnectorURI="tcp://masterhost:62001" shutdownOnMasterFailure="false"> ... <transportConnectors> <transportConnector uri="tcp://slavehost:61616"/> </transportConnectors> </broker>
如果你使用共享文件系统,那么你可以使用Shared File System Master Slave。如下图所示:
客户端使用failover Transport 去连接 broker,例如:
failover:(tcp://broker1:61616,tcp://broker2:6161 7,broker3:61618)
<broker useJmx="false" xmlns="http://activemq.org/config/1.0"> <persistenceAdapter> <journaledJDBC dataDirectory="/sharedFileSystem/broker"/> </persistenceAdapter> … </broker>
JDBC Master Slave的工作原理跟Shared File System Master Slave类似,只是采用了数据库作为持久化存储
客户端调用:
failover:(tcp://broker1:61616,tcp://broker2:616167,broker3:61618)
<beans> <broker xmlns="http://activemq.org/config/1.0" brokerName="JdbcMasterBroker"> <persistenceAdapter> <jdbcPersistenceAdapter dataSource="#mysql-ds"/> </persistenceAdapter> </broker> <bean id="mysql-ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/test?relaxAutoCommit=true"/> <property name="username" value="username"/> <property name="password" value="passward"/> <property name="poolPreparedStatements" value="true"/> </bean> </beans>
Broker clusters ,网络型中介(network of brokers)
连接到网络代理的两种方式:
Static:(uri1,uri2,uri3,…)?key=value 或是Failover:(uri1, … , uriN)?key=value
下面给出一个配置实例:
<networkConnectors> <networkConnector name=”local network” uri=”static://(tcp://remotehost1:61616,tcp://remotehost2:61616)”/> </networkConnectors>
一般activemq的Master Slave是基于KAHADB的阻塞来做的,先看一下原理
注意红色加粗的地方,这是传统的Master Slave的一个缺陷。这样做太不安全了!
下面给出核心配置:
master配置(不要忘了改conf目录下的jetty.xml文件中的端口)
<transportConnectors> <!-- DOS protection, limit concurrent connections to 1000 and frame size to 100MB --> <transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/> <transportConnector name="amqp" uri="amqp://0.0.0.0:5672?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/> <transportConnector name="stomp" uri="stomp://0.0.0.0:61613?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/> <transportConnector name="mqtt" uri="mqtt://0.0.0.0:1883?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/> <transportConnector name="ws" uri="ws://0.0.0.0:61614?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/> </transportConnectors>
<persistenceAdapter> <kahaDB directory="/localhost/kahadb"/> </persistenceAdapter>
<transportConnectors> <!-- DOS protection, limit concurrent connections to 1000 and frame size to 100MB --> <transportConnector name="openwire" uri="tcp://0.0.0.0:61617?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/> <transportConnector name="amqp" uri="amqp://0.0.0.0:5682?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/> <transportConnector name="stomp" uri="stomp://0.0.0.0:61623?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/> <transportConnector name="mqtt" uri="mqtt://0.0.0.0:1903?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/> <transportConnector name="ws" uri="ws://0.0.0.0:61634?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/> </transportConnectors>
<persistenceAdapter> <kahaDB directory="/localhost/kahadb"/> </persistenceAdapter>
<kahaDB directory=“/localhost/kahadb”/>
中的db lock住,它就成了mater,而此时另一个实例会处于“pending”状态。
当master宕机后slave会自动启动转变为master。
当宕掉的master再次被启动后然后变成slave挂载在原先的slave下面变成slave’
客户端的访问代码如下,只要改spring中的配置,代码层无需发动:
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="failover:(tcp://192.168.0.101:61616,tcp://192.168.0.101:61617)" <property name="useAsyncSend" value="true" /> <property name="alwaysSessionAsync" value="true" /> <property name="useDedicatedTaskRunner" value="false" /> </bean>
搭个集群,还要共享磁盘???见了鬼去了,这种方案肯定不是最好的!
因此,从ActiveMQ5.10开始出现了使用ZooKeeper来进行Master/Slave搭建的模式。
搭建时请务必注意下面几个问题:
搭建ZooKeeper,你可以搭建3个ZooKeeper实例也可以只用1个,如果出于高可用性考虑建议使用3台ZooKeeper。
下载ZK,我们在这边使用的是“zookeeper-3.4.6”,把它解压到Linux服务器上,在其conf目录下生成zoo1.cfg文件 ,内容如下:
tickTime=2000 initLimit=10 syncLimit=5 dataDir=/opt/zk/data/1 clientPort=2181 #server.1=127.0.0.1:2887:3887 #server.2=127.0.0.1:2888:3888 #server.3=127.0.0.1:2889:3889
如果你需要在本地搭建2N+1的ZooKeeper,那你必须依次在data目录下建立2、3两个文件夹并且在每个文件夹下都有一个myid的文件,文件的内容依次也为2,3,然后把下面注释掉的3行server.1 server.2 server.3前的#注释符放开。
因此data目录下的目录名和myid中的内容对应的就是server.x这个名字。
tickTime=2000 initLimit=10 syncLimit=5 dataDir=/opt/zk/data/1 clientPort=2181 server.1=127.0.0.1:2887:3887 server.2=127.0.0.1:2888:3888 server.3=127.0.0.1:2889:3889
按照2N+1,你必须至少建立3个ActiveMQ实例。在搭建前我们先来看一下我们的集群规划吧
本例中,我们只使用一个ZK节点,即2181端口来做MQ1-3的数据文件共享。
核心配置
以下是传统的Master/Slave配置
<persistenceAdapter> <kahaDB directory="/localhost/kahadb"/> </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" /> </persistenceAdapter>
依次把3台mq实例均配置成replicatedLevelDB即可,在此配置中有一个zkPath,笔者使用的是默认配置。你也可以自行加上如:
<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" /> </persistenceAdapter>
master slave一旦启动后,在客户端的配置就很简单了,如下:
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name=“brokerURL” value=“failover:(tcp://192.168.0.101:61616,tcp://192.168.0.101:61617, tcp://192.168.0.101:61618)" /> <property name="useAsyncSend" value="true" /> <property name="alwaysSessionAsync" value="true" /> <property name="useDedicatedTaskRunner" value="false" /> </bean>
所谓cluster即负载匀衡,它负载的是“用户”的请求,此处的用户就是调用端
cluster的用法就是用来分散用户的“并发”请求的。前面説过,ActiveMQ的Cluster有两种:
核心配置
<networkConnectors> <networkConnector uri="static:(tcp://ymklinux:61617)" duplex=“false"/> </networkConnectors>
在61616中的配置
<networkConnectors> <networkConnector uri="static:(tcp://ymklinux:61617)" duplex=“false"/> </networkConnectors>
<networkConnectors> <networkConnector uri="static:(tcp://ymklinux:61616)" duplex=“false"/> </networkConnectors>
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name=“brokerURL” value=“failover:(tcp://192.168.0.101:61616,tcp://192.168.0.101:61617)" /> <property name="useAsyncSend" value="true" /> <property name="alwaysSessionAsync" value="true" /> <property name="useDedicatedTaskRunner" value="false" /> </bean>
优点
当调用端连接上任意一个节点发送了一条消息,比如说往NODE A发送了一条消息。
此时消费端还没来得及消费NODE A上的消息该节点就发生宕机了。此时调用端因为有failover所以它会自动连上另一个节点(NODE B)而该节点上是不存在刚才“发送的消息”的,因为刚才发送到NODE A的消息只在NODE A上,没有同步到NODE B上。
因为只有Master Slave会做主从间的消息同步,这是一个致命伤。
具体实验步骤:
我们知道:
由于这涉及到两个组6个ActiveMQ的实例配置,如果把6个配置全写出来是完全没有必要的,因此我就把配置分成两组来写吧。每个组的配置对于其组内各个节点都为一致的,除去那些个端口号。Group1的配置(保持6个实例中brokerName全部为一致)
<networkConnectors> <networkConnector uri="static:(tcp://ymklinux:61619,tcp://ymklinux:61620,tcp://ymklinux:61621)" duplex=“false"/> </networkConnectors>
<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>
<networkConnectors> <networkConnector uri="static:(tcp://ymklinux:61616,tcp://ymklinux:61617,tcp://ymklinux:61618)" duplex=“false"/> </networkConnectors>
<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>
客户端:
<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台实例(乖乖,好长一陀)
是的,以上的方案存在着这一漏洞!来看原理!
对于上述情况,当61616宕机后,如果此时请求被failover到了group1中任意一个节点时,此时消费端完全可以拿到因为宕机而未被消费掉的消费。
对于上述情况,当61616宕机后,如果此时请求被failover到了group2中任意一个节点时,此时因为group2中并未保存group1中的任何消息,因此只要你的请求被转发到另一个group中,消费端是决无可能去消费本不存在的消息的。这不是有可能而是一定会发生的情况。因为在生产环境中,请求是会被自由随机的派发给不同的节点的。
我为什么要提这个缺点、要否认之前的篇章? 我就是为了让大家感受一下网上COPY复制还是错误信息的博文的严重性,那。。。怎么做是真正完美的方案呢。。。
我们来看,如果我们把两个group间可以打通,是不是就可以在上述情况发生时做到failover到group2上时也能消费掉group1中的消息了呢?
怎么做?很简单,其实就是一个参数
什么叫duplex=true?
它就是用于mq节点间双向传输用的一个功能,看下面的例子
这就是duplex的使用方法,我很奇怪,竟然这么多人COPY 那错的1-2篇博文,但是就是没有提这个duplex的值,而且都设的是false。
duplex的作用就是mq节点1收到消息后会变成一个sender把消息发给节点2。
此时客户如果连的是节点2,也可以消息节点1中的消息,因此我给它起了一个比英译中更有现实意义的名称我管它叫“穿透”。
下面来看用“穿透机制”改造的真正完美方案吧。
考虑到消费端可能会发生:
当Group1中有未消费的数据时时,消费端此时被转派到了Group2中的任意一个节点。
因此,在配置时需要进行如下的穿透:
按照这个思路我们在各Group中的各节点中作如下配置即可:
Group1中的配置
<networkConnectors> <networkConnector uri="static:(tcp://ymklinux:61619,tcp://ymklinux:61620,tcp://ymklinux:61621)" duplex=“true"/> </networkConnectors>
<networkConnectors> <networkConnector uri="static:(tcp://ymklinux:61616,tcp://ymklinux:61617,tcp://ymklinux:61618)" duplex=“true"/> </networkConnectors>
从生产环境的高可用性来説,如果需要使用完美解决方案的话我们至少需要以下这些实体机
2台实体机