ActiveMQ與HA架構(master/slave)


   本文轉載自:http://shift-alt-ctrl.iteye.com/blog/2069250

 

 HA(高可用性)幾乎在所有的架構中都需要有一定的保證,在生產環境中,我們也需要面對broker失效、網絡故障等各種問題,ActiveMQ也不例外。activeMQ作為消費分發和存儲系統,它的HA模型只有master-slave,我們通過broker節點“消息互備”來達成設計要求。M-S架構中,只有master開啟transportConnector,slave不開啟,所以客戶端只能與master通訊,客戶端無法與slave建立連接。

 

    Client端與master交互並生產和消息消息,並且有一個或者多個slave與其保持同步,如果master失效,我們希望slave能夠自動角色轉換並接管服務,並且故障轉移的過程不能影響Client對消息服務的使用(failover)。ActiveMQ Broker不僅僅負責消息的分發,還需要存儲消息,根據存儲機制的不同,master-slave模式分為兩種:“Shared nothing”和“Shared storage”。其中“Shared nothing”表明每個broker有各自的存儲機制,各個broker之間無任何數據共享,這是最簡單的部署方案,當然也是數據可靠性最低的,如果一個broker存儲設備故障將會導致此broker中的數據需要重建,master與slave之間的狀態感知,是通過TCP通信來實現。“Shared storage”是推薦的架構方案,master與slave之間共享遠端存儲系統(比如JDBC Storage,SAN分布式文件系統等),master與slave通過獲取Storage的排他鎖狀態來感知狀態,獲取鎖的broker作為master並負責與Client數據交互,當鎖失效后slave之間通過鎖競爭來產生新的master,在最新的架構中,zookeeper也可以很方便的集成到ActiveMQ中。

 

    master-slave並不是大規模消息系統的擴展方案,它只是解決broker節點的HA問題,稍后我們會介紹“Forward Brige”模式在activeMQ分布式系統中的應用。

 

一. Share nothing storage master/slave

    最簡單最典型的master-slave模式,master與slave有各自的存儲系統,不共享任何數據。master接收到的所有指令(消息生產,消費,確認,事務,訂閱等)都會同步發送給slave。在slave啟動之前,首先啟動master,在master有效時,salve將不會創建任何transportConnector,即Client不能與slave建立鏈接;如果master失效,slave是否接管服務是可選擇的(參見下文配置)。在master與slave之間將會建立TCP鏈接用來數據同步,如果鏈接失效,那么master認為slave離線。

 

 

    對於持久化消息,將會采用同步的方式(sync)在master與slave之間備份,master接受到消息之后,首先發給slave,slave存儲成功后,master才會存儲消息並向producer發送ACK。

 

    當master失效后,slave有兩種選擇:

    1) 關閉:如果slave檢測到master失效,slave實例關閉自己。此后管理員需要手動依次啟動master、slave來恢復服務。

    2) 角色轉換: slave將自己提升為master,並初始化transportConnector,此后Client可以通過failover協議切換到Slave上,並與slave交互。

Java代碼   收藏代碼
  1. //Client使用failover協議來與有效的master交互  
  2. //master地址在前,slave在后,randomize為false讓Client優先與master通信  
  3. //如果master失效,failover協議將會嘗試與slave建立鏈接,並依此重試  
  4. failover://(tcp://master:61616,tcp://slave:61616)?randomize=false  

 

    “Shared nothing”模式下,有很多局限性。master只能有一個slave,而且slave不能繼續掛載slave。如果slave較晚的接入master,那么master上已有的消息不會同步給slave,只會同步那些slave接入之后的新消息,那也意味着slave並沒有完全持有全局的所有消息;所以如果此時master失效,slave接入之前的消息有丟失的風險。如果一個新的slave接入master,或者一個失效時間較長的舊master接入新master,通常會首先關閉master,然后把master上所有的數據Copy給slave(或舊master),然后依次啟動它們。事實上,ActiveMQ沒有提供任何有效的手段,能夠讓master與slave在故障恢復期間,自動進行數據同步。

 

    Master配置:

Java代碼   收藏代碼
  1. <broker brokerName="master" waitForSlave="true" shutdownOnSlaveFailure="false" waitForSlaveTimeout="600000" useJmx="false" >  
  2.     ...  
  3.     <transportConnectors>  
  4.       <transportConnector uri="tcp://localhost:61616"/>  
  5.     </transportConnectors>  
  6. </broker>  

 

    其中shutdownOnSlaveFailure默認為false,即當slave失效時,master將繼續服務,否則master也將關閉。waitForSlave表示當master啟動之后,是否等待slave接入,如果為true,那么master將會等待waitForSlaveTimeout毫秒數,直到有slave接入之后master才會初始化transportConnector,此間Client無法與master交互;如果等待超時,則有shutdownOnSlaveFailure決定master是否關閉。

 

    Slave配置:

Java代碼   收藏代碼
  1. <broker brokerName="slave" shutdownOnMasterFailure="false">  
  2.     <services>  
  3.       <masterConnector remoteURI= "tcp://master:61616"/>  
  4.     </services>  
  5.     ....  
  6.     <transportConnectors>  
  7.       <transportConnector uri="tcp://localhost:61616"/>  
  8.     </transportConnectors>  
  9. </broker>  

 

    shutdownOnMasterFailure表示當master失效后,slave是否自動關閉,默認為false,表示slave將提升為master。

 

    不過我們需要注意,master與slave之間通過Tcp鏈接感知對方的狀態,基於鏈接感知狀態的“三節點網絡”(Client,Master,slave),結果總是不可靠的;如果master與slave實例都有效,只是master與slave之間的網絡“阻斷”,此時slave也會認為master失效,如果slave提升為master,對於Client而言將會出現2個master的“幻覺”。更壞的情況是,部分Client與舊master之間也處於網絡阻斷情況,那么就會出現部分Client鏈接在新master上,其他的Client鏈接在舊master上,數據的一致性將處於失控狀態。(split-brain)為了避免出現“腦裂”現象,我們通常將“shutdownOnMasterFailure”(slave上)、“shutdownOnSlaveFailure”(master上)兩個參數不能同時設置為false。



  

    因為這種模式下各種局限性,“Shared nothing”模式已經被active所拋棄,將在5.8+之后版本中移除。不過對於小型應用而言,這種模式帶來的便捷性也是顯而易見的。

 

 

二. Shared storage master/slave 

    在上文中,我們已經了解了“Shared nothing”模式下的局限性,在企業級架構中它將不能作為一種可靠的方案而實施。ActiveMQ官方推薦“Shared storage”模式作為首選方案,並提供了多個優秀的存儲策略,其中kahadb、levedbDB、JDBC Store等都可以便捷的接入。

 

    “Shared storage”允許集群中有多個slave共存,因為存儲數據在salve與master之間共享(物理共享),所以當master失效后,slave自動接管服務,而無需手動進行數據的Copy與同步,也無需master與slave之間進行任何額外的數據交互,因為master存儲數據之后,它們在任何時候對slave都是可見的。master與slave之間,通過共享文件的“排他鎖”或者分布式排他鎖(zookeeper)來決定broker的狀態與角色,獲取鎖權限的broker作為master,如果master失效,它必將失去鎖權限,那么slaves將通過鎖競爭來選舉新master,沒有獲取鎖權限的broker作為slave,並等待鎖的釋放(間歇性嘗試獲取鎖)。當然slaves仍然不能為Client服務, 它只為故障轉移做准備。

 

    在此需要明確一個問題,“Shared storage”模式只會共享“持久化”類型的消息;對於非持久化消息將不能從從中收益,它們不會在slaves中備份,這也意味着如果master失效,即使slave接管了服務,此前保存在master上的非持久化消息也將丟失。通常,我們在“Shared storage”模式中,額外的配置一個插件,強制將所有消息轉換成持久化類型,這樣所有的消息都可以在故障恢復之后不會丟失。

 

Java代碼   收藏代碼
  1. <broker>    
  2.     <plugins>    
  3.         <!-- 將所有消息的傳輸模式,修改為"PERSISTENT" -->    
  4.       <forcePersistencyModeBrokerPlugin persistenceFlag="true"/>    
  5.     </plugins>    
  6. </broker>  

 



 

    1. Shared File System master/slaves

    基於共享文件系統的master/slaves模式,此處所謂的“共享文件系統”目前只能是基於POSIX接口可以訪問的文件系統,比如本地文件系統或者SAN分布式共享文件系統(比如:glusterFS);對於broker而言,啟動時將會首先獲取存儲引擎的文件鎖,如果獲取成功才能繼續初始化transportConnector,否則它將一直嘗試獲取鎖(tryLock),那么對於共享文件系統而言,需要嚴格確保任何時候只能有一個進程獲取排他鎖,如果你選擇的SAN文件系統不能保證此條件,那么將不能作為master/slavers的共享存儲引擎。

 

    “Shared File System”這種方式是最常用的模式,架構簡單,可靠實用。我們只需要一個SAN文件系統即可。

   

    在這種模式下,master與slave的配置幾乎可以完全一樣。如下示例中,我們使用一個master和一個slave,它們部署在一個物理機器上,使用本地文件系統作為“共享文件系統”。(僅作示例,不可用於生產環境)

    Master/Slave配置:

Java代碼   收藏代碼
  1. <broker xmlns="http://activemq.apache.org/schema/core" brokerName="broker-locahost-0" useJmx="false">  
  2.       
  3.     <persistenceAdapter>  
  4.         <levelDB directory="/data/leveldb"/>  
  5.     </persistenceAdapter>  
  6.     <tempDataStore>  
  7.         <levelDB directory="/data/leveldb/tmp"/>  
  8.     </tempDataStore>  
  9.   
  10.     <!--  
  11.     <persistenceAdapter>  
  12.         <kahaDB directory="/data/kahadb"/>  
  13.     </persistenceAdapter>  
  14.     <tempDataStore>  
  15.         <pListStoreImpl directory="/data/kahadb/tmp"/>  
  16.     </tempDataStore>  
  17.     -->  
  18.     <transportConnectors>  
  19.         <!-- slave -->  
  20.         <!--  
  21.         <transportConnector name="openwire" uri="tcp://0.0.0.0:51616?maximumConnections=1000&amp;wireformat.maxFrameSize=104857600"/>  
  22.         -->  
  23.         <transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&amp;wireformat.maxFrameSize=104857600"/>  
  24.     </transportConnectors>  
  25. </broker>  

 

    對於Client端而言,可以使用failover協議來訪問broker:

Java代碼   收藏代碼
  1. failover://(tcp://localhost:61616,tcp://localhost:51616)?randomize=false  

 

    2.  JDBC Store master/slaves

    顯而易見,數據存儲引擎為database,activeMQ通過JDBC的方式與database交互,排他鎖使用database的表級排他鎖,其他原理基本上和1)一致。JDBC Store相對於日志文件而言,通常認為是低效的,盡管數據的可見性較好,但是database的擴容能力非常的弱,無法良好的適應在高並發、大數據情況下(嚴格來說,單組M-S架構是無法支持大數據的),況且ActiveMQ的消息通常存儲時間較短,頻繁的寫入,頻繁的刪除,都是性能的影響點。我們通常在研究activeMQ的存儲原理時使用JDBC Store,或者在中小型應用中對數據一致性(可靠性,可見性)要求較高的環境中使用:比如訂單系統中交易流程支撐系統等。但是因為JDBC架構的實施簡便,易於管理,我們仍然傾向於首選這種方式。

 

    在使用JDBC Store之前,必須有一個穩定的database,且指定授權給acitvemq中的鏈接用戶具有“創建表”和普通CRUD的權限。master與slave中的配置文件基本一樣,開發者需要注意brokerName和brokerId全局不可重復。此外還需要把相應的jdbc-connector的jar包copy到${acitvemq}/lib/optional目錄下。

 

    Master/Slave配置:

Java代碼   收藏代碼
  1. <broker brokerName="broker-locahost-0">  
  2.       
  3.     <persistenceAdapter>  
  4.         <jdbcPersistenceAdapter dataSource="#mysql-ds"/>  
  5.     </persistenceAdapter>  
  6.   
  7.     <transportConnectors>  
  8.         <!-- DOS protection, limit concurrent connections to 1000 and frame size to 100MB -->  
  9.         <transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&amp;wireformat.maxFrameSize=104857600"/>  
  10.     </transportConnectors>  
  11.   
  12. </broker>  
  13.   
  14. <bean id="mysql-ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">  
  15.     <property name="driverClassName" value="com.mysql.jdbc.Driver"/>  
  16.     <property name="url" value="jdbc:mysql://localhost:3306/activemq?relaxAutoCommit=true"/>  
  17.     <property name="username" value="root"/>  
  18.     <property name="password" value="root"/>  
  19.     <property name="poolPreparedStatements" value="true"/>  
  20. </bean>  

 

    JDBC Store有個小小的問題,就是臨時文件無法保存在database中,我們不能在<tmpDataStore>使用JDBC Store;所以臨時文件還是只能保存在broker本地。(即非持久消息不會保存在database中,只會保存在master上)

 

三. Replicated LevelDB Store

    基於復制的LevelDB Store,這是ActiveMQ全力打造的HA存儲引擎,也是目前比較符合“Master-slave”架構模型的存儲方案,此特性在5.9+版本中支持。我們從1)/2)兩個方案中可見,“Shared Storage”模式只是利用了“一些小伎倆”,並不符合廣泛意義上的“master-slave”模型(在存儲上,和通訊機制上)。不過,“Replicated LevelDB Store”做到了!!

 

    “Replicated LevelDB”也同樣允許有多個Slaves,而且Slaves的個數有了約束性的限制,這歸結於其使用zookeeper作為Broker master選舉。每個Broker實例將消息數據保存本地(類似於“Shared nothing”),它們之間並不共享任何數據,所以把“Replicated LevelDB”歸類為“Shared storage”並不妥當。

 

    當Broker啟動時,它首先向zookeeper注冊自己的信息(brokerName,消息日志的版本戳等),如果此時group中沒有其他broker實例,並阻塞初始化過程,等到足夠多的broker加入group;當brokers的數量達到“replicas的多數派"時,開始選舉,選舉將會根據“消息日志的版本戳”、“權重"的大小決定,即“版本戳”越大(數據最新)、權重越高的broker優先成為master,其他broker作為slave並跟隨master。當一個broker成為master時,它會向zookeer注冊自己的sync地址信息;此后slaves首先根據sync地址與master建立鏈接,並同步消息文件(download)。當足夠多的slave數據同步結束后,master將初始化transportConnector,此后Client將可以與master進行數據交互。

Java代碼   收藏代碼
  1. <broker xmlns="http://activemq.apache.org/schema/core" brokerName="mq-group" brokerId="mq-group-0" dataDirectory="${activemq.data}">  
  2.   
  3.     <persistenceAdapter>  
  4.         <replicatedLevelDB directory="${activemq.data}"  
  5.           replicas="2"  
  6.           bind="tcp://127.0.0.1:0"  
  7.           zkAddress="127.0.0.1:2181"  
  8.           zkSessionTmeout="30s"  
  9.           zkPath="/activemq/leveldb-stores"  
  10.           hostname="broker0" />  
  11.      </persistenceAdapter>  
  12.            
  13. </broker>  

 

    Master-slaves集群中,所有的broker必須具有相同的brokerName,它作為group域來限定集群的成員,brokerId可以不同,它僅作為描述信息。“replicas”參數非常重要,默認為3,表示消息最多可以備份在幾個broker實例上,同是只有當“replicas/2 + 1”個broker存活時(包含master),集群才有效,才會選舉master和備份消息,此值必須>=2。Client發送給Master的持久化消息(包括ACK和事務),master首先在本地保存,然后立即同步(sync)給選定的(replicas/2)個slaves,只有當這些節點也同步成功后,此消息的交互才算結束;對於剩下的replicas個節點,master采用異步的方式(async)轉發。這種設計要求,可以保證集群中消息的可靠性,只有當(replicas/2 + 1)個節點物理故障,才會有丟失消息的風險。通常replicas為3,這要求開發者需要至少部署3個broker實例。如果replicas過大,會嚴重影響master的吞吐能力,因為它在sync消息的過程中會消耗太多的時間。



  

 

    如果集群故障,在重啟broker實例時,建議首先查看每個broker中查看LevelDB日志文件的版本戳(文件名為16進制數字),並優先啟動版本戳較大的實例。(因為replicas多數派的約束,隨機重啟也不會有太大的問題)。但是不得隨意調小replicas的值,如果你確實需要修改,那就首先關閉集群,一定優先啟動版本戳最大的broker。

 

    盡管集群對zookeeper的操作並不是很多,但是我們還是希望不要接入負載過高的zookeeper集群,以免給消息服務引入不穩定因素。通常zookeeper集群至少需要3個實例,才能保證zookeeper本身的高可用性。

 

    其中bind屬性表示當此broker實例成為master時,開啟一個socket地址,此后slave可以通過此地址與其同步數據。

 

    我們還需要為Replicated LevelDB配置zookeeper的地址和path,其中path下用來存儲group中所有broker的注冊信息,此值在group中所有的broker上都要一樣。“hostname”用來描述當前機器的核心信息,通常為機器IP。如果你在本機構建偽分布式,那么需要在系統hosts文件中使用轉義。

Java代碼   收藏代碼
  1. 127.0.0.1   broker0  
  2. 127.0.0.1   broker1  
  3. 127.0.0.1   broker2  

 

    對於Client端而言,仍然需要使用failover協議,而且協議中需要包含group中所有broker的鏈接地址。

Java代碼   收藏代碼
  1. failover://(tcp://localhost:61616,tcp://localhost:51616,tcp://localhost:41616)?randomize=false  

 

    和其他模式一樣,對於非持久化消息仍然只會保存在master上,當master失效后,消息將會丟失。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM