轉載:http://blog.csdn.net/lifetragedy/article/details/51869032
ActiveMQ的集群
內嵌代理所引發的問題:
- 消息過載
- 管理混亂
如何解決這些問題——集群的兩種方式:
- Master slave
- Broker clusters
ActiveMQ的集群有兩種方式:
- MASTER/SLAVE模式
- Cluster模式
Pure Master Slave

Pure master slave的工作方式:
當master broker失效的時候。Slave broker 做出了兩種不同的相應方式
- 啟動network connectors和transport connectors
- slave broker停止,slave broker只是復制了master broker的狀態
在任何情況下我們都應該先嘗試連接master,故在客戶端我們這樣配置:
- failover://(tcp://masterhost:61616,tcp://slavehost:61616)?randomize=false
Pure Master Slave具有以下限制:
- 只能有一個slave broker連接到master broker。
- 在因master broker失效后slave才接管(保證消息完全拷貝)
- 要想恢復master,停止slave,拷貝slave中的數據文件到master中,然后重啟;
- Master broker不需要特殊的配置。Slave broker需要進行以下配置:
- <broker masterConnectorURI="tcp://masterhost:62001" shutdownOnMasterFailure="false">
- ...
- <transportConnectors>
- <transportConnector uri="tcp://slavehost:61616"/>
- </transportConnectors>
- </broker>
Shared File System Master Slave
如果你使用共享文件系統,那么你可以使用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>
其中/sharedFileSystem是文件共享的系統文件目錄
JDBC Master Slave
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-靜態
Broker clusters ,網絡型中介(network of brokers)
連接到網絡代理的兩種方式:
- 靜態的方法配置訪問特定的網絡代理
- 發現中介(agents)動態的探測代理
靜態網絡代理
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>
幾種集群對比
- broker的集群在多個broker之前fail-over和 load-balance,master-slave能fail-over,但是不能load- balance
- 消息在多個broker之間轉發,但是消息只存儲在一個broker上,一旦失效必須重啟,而主從方式master失效,slave實時備份消息。
- jdbc方式成本高,效率低
- master-slave方式中pure方式管理起來麻煩
因此,我們把MASTER/SLAVE和BROKER CLUSTER兩者相結合,可以得到一個完全解決方案:即又可以做到集群又可以做到任何一個BROKER如果發生宕機節點消息也不丟失。
Master/Slave集群搭建-傳統式
一般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>
slave配置(不要忘了改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: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>
- 把master conf 目錄中的jetty.xml中的端口改成8161
- 把slave conf 目錄中的jetty.xml中的端口改成8162
此時把master和slave先后啟動,其實你也可以不用管順序,誰先啟動誰會先把
<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>
Master/Slave集群搭建-基於ZooKeeper
搭個集群,還要共享磁盤???見了鬼去了,這種方案肯定不是最好的!
因此,從ActiveMQ5.10開始出現了使用ZooKeeper來進行Master/Slave搭建的模式。
搭建時請務必注意下面幾個問題:
- 參於master slave組網的配置中<broker xmlns=“http://activemq.apache.org/schema/core” brokerName=“zk_cluster_nodes“必須一至。
- ActiveMQ至少需要3個實例(2N+1公式),只要不符合2N+1,master slave集群會發生集群崩潰
Master/Slave集群搭建-基於ZooKeeper搭建
搭建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
在ZK的data目錄下建立一個目錄,名為“1”,並在其下建立一個文件,文件名為“myid”並使其內容為1
如果你需要在本地搭建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>
基於ZK的配置
- <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實例的brokerName必須一致,要不然你會在集群啟動時出現:
Not enough cluster members when using LevelDB replication
這樣的錯誤。
或者在集群啟動后當master宕機slave被promote成master時發生集群崩潰。
依次把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"
- />
- lt;/persistenceAdapter>
網上所有教程都沒有說明這個zkPath到底是一個什么東西,害得一堆的使用者以為這是一個目錄,以為用ZK做mq的集群也需要“共享”一塊磁盤空間。
這個zkPath代表zookeeper內的“路徑”,即你運行在2181端口的zk內的“尋址節點”,類似於JNDI。如果你沒有這個zkPath,默認它在zk內的尋址節點為“/default”。加入到某一組master slave中的mq的實例中的zkPath必須完全匹配。
這邊還有一個replicas=“3”的設置 ,這邊的數字指的就是ActiveMQ實例的節點數,它需要滿足2N+1,因此你也可以5台(1拖4)。
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>
你可以如此實驗:
- 生產和消費分成兩個程序。
- 生產發一條消息,注意看console會輸出如“[INFO] Successfully reconnected to tcp://192.168.0.101:61616”
- 手工停止61616這台實例,然后你會發覺61617或者 是61618中有一台被promoted to master
- 然后運行consumer,consumer會連上被promoted的實例並繼續消費剛才那3條消息
是為。。。high available。
Master/Slave集群搭建-Broker Cluster搭建
所謂cluster即負載勻衡,它負載的是“用戶”的請求,此處的用戶就是調用端
cluster的用法就是用來分散用戶的“並發”請求的。前面説過,ActiveMQ的Cluster有兩種:
- 動態式(multicast)
- 靜態式(static)
在實際應用場景中”靜態方式“被大量使用,動態式使用的是multicast即組播式,基本已經被淘汰,因此我們主要基於靜態方式來做cluster講解。
Master/Slave集群搭建-Broker Cluster搭建方式
核心配置
- <networkConnectors>
- <networkConnector uri="static:(tcp://ymklinux:61617)" duplex=“false"/>
- </networkConnectors>
請維持各cluste節點間不同的持久化方式(你也可以使用kahadb,但不要做成和master slave一樣的排它鎖方式啊。
在61616中的配置
- <networkConnectors>
- <networkConnector uri="static:(tcp://ymklinux:61617)" duplex=“false"/>
- </networkConnectors>
在61617中的配置
- <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>
Master/Slave集群搭建-Broker Cluster和Master Slave對比
優點
- 自動分發調用端請求
- 流量分散
缺點
當調用端連接上任意一個節點發送了一條消息,比如說往NODE A發送了一條消息。
此時消費端還沒來得及消費NODE A上的消息該節點就發生宕機了。此時調用端因為有failover所以它會自動連上另一個節點(NODE B)而該節點上是不存在剛才“發送的消息”的,因為剛才發送到NODE A的消息只在NODE A上,沒有同步到NODE B上。
因為只有Master Slave會做主從間的消息同步,這是一個致命傷。
具體實驗步驟:
- send一條消息到cluster,並觀察send時連接的是哪個節點
- 直接把該節點進程殺掉
- 用消費端連上集群
- 觀察消費端是否拿得到消息
魚與熊掌兼得法-完美解決方案
我們知道:
- master/slave模式下,消息會被逐個復制
- 而cluster模式下,請求會被自動派發
那么可不可以把兩者集成起來呢?答案是有的,網上所謂的獨創。。。統統是錯的!!對,因為我全試驗過了我敢這么説,寫得都是些個啥呀。。。一個個還COPY不走樣,全錯了而且。
我這個才叫獨創,來看原理。
MASTER SLAVE+BROKER CLUSTER的搭建
- 我們使用ZK搭建MASTER SLAVE
- 我們使用BROKER CLUSTER把兩個“組”合並在一起
先來看下面的集群規划
MASTER SLAVE+BROKER CLUSTER的搭建-Group1的配置
由於這涉及到兩個組6個ActiveMQ的實例配置,如果把6個配置全寫出來是完全沒有必要的,因此我就把配置分成兩組來寫吧。每個組的配置對於其組內各個節點都為一致的,除去那些個端口號。Group1的配置(保持6個實例中brokerName全部為一致)
- <networkConnectors>
- <networkConnector uri="static:(tcp://ymklinux:61619,tcp://ymklinux:61620,tcp://ymklinux:61621)" duplex=“false"/>
- </networkConnectors>
可以看到這邊的broker cluster的配置是用來確保每一台都可以和Group2中的各個節點保持同步
- <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的配置
- <networkConnectors>
- <networkConnector uri="static:(tcp://ymklinux:61616,tcp://ymklinux:61617,tcp://ymklinux:61618)" duplex=“false"/>
- </networkConnectors>
可以看到這邊的broker cluster的配置是用來確保每一台都可以和Group1中的各個節點保持同步
- <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的搭建-客戶端
客戶端:
- <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的搭建-實驗
- 使用生產端任意發送3條消息。
- 生產端連上了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中的配置
- <networkConnectors>
- <networkConnector uri="static:(tcp://ymklinux:61619,tcp://ymklinux:61620,tcp://ymklinux:61621)" duplex="true"/>
- </networkConnectors>
Group2中的配置
Group2中的每一個節點都不再需要來一句以下這樣的東西了:
- <networkConnectors>
- <networkConnector uri="static:(tcp://ymklinux:61616,tcp://ymklinux:61617,tcp://ymklinux:61618)" duplex="true"/>
- </networkConnectors>
因為,你設的是duplex=true,它相當於下面幾行的效果:
- <!-- 在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只作節點信息、消息同步使用,不會受太多並發訪問之苦。
還有一種集群方式留給大家自己去練習,很簡單,就是幾個節點間無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即可,這個架構不難,動一下手就會了。