序:ActiveMQ高性能方案的不足
那麼有的讀者可能會問,既然ActiveMQ的高性能方案中多個節點同時工作,在某個節點異常的情況下也不會影響其他節點的工作。這樣看來,ActiveMQ的高性能方案已經避免了單點故障,那麼我們爲什麼還需要討論ActiveMQ的高可用方案呢?
爲了回答這個問題,我們先回過頭來看看ActiveMQ高性能方案的一些不足。假設如下的場景:ActiveMQ A和AcitveMQ B兩個服務節點已建立了Network Bridge;並且Producer1 連接在ActiveMQ A上,按照一定週期發送消息(隊列名:Queue/testC);但是當前並沒有任何消費者Consumer連接在任何ActiveMQ服務節點上接收消息。整個場景如下圖所示:
在發送了若干消息後,我們查看兩個節點ActiveMQ服務節點的消息情況,發現ActiveMQ A並沒有把隊列Queue/testC中的消息同步到ActiveMQ B。原來AcitveMQ Network Bridge的工作原則是:只在服務節點間傳輸需要傳輸的消息,這樣做的原因是爲了儘量減少AcitveMQ集羣網絡中不必要的數據流量。在我們實驗的這種情況下並沒有任何消費者在任何ActiveMQ服務節點上監聽/訂閱隊列Queue/testC中的消息,所以消息並不會進行同步。
那麼這樣的工作機制帶來的問題是,當沒有任何消費者在任何服務節點訂閱ActiveMQ A中隊列的消息時,一旦ActiveMQ A由於各種異常退出,後來的消費者就再也收不到消息,直到ActiveMQ A恢復工作。所以我們需要一種高可用方案,讓某一個服務節點能夠7 * 24小時的穩定提供消息服務。
ActiveMQ 熱備方案
1、基於共享文件系統的熱備方案
1.1、方案介紹
基於共享文件系統的熱備方案可以說是ActiveMQ消息中間件中最早出現的一種熱備方案。它的工作原理很簡單:讓若干個ActiveMQ服務節點,共享一個文件系統。當某一個ActiveMQ服務搶佔到了這個文件系統的操作權限,就給文件系統的操作區域加鎖;其它服務節點一旦發現這個文件系統已經被加鎖(並且鎖不屬於本進程),就會自動進入Salve模式。
ActiveMQ早期的文件存儲方案、KahaDB存儲方案、LevelDB存儲方案都支持這個工作模式。當某個ActiveMQ節點獲取了文件系統的操作權限後,首先做的事情就是從文件系統中恢復內存索引結構:KahaDB恢復BTree結構;LevelDB恢復memTable結構。
因爲本專題講解的技術體系都是工作在Linux操作系統上,所以爲多個ActiveMQ提供共享文件系統方案的第三方文件系統都必須支持POSIX協議,這樣Linux操作系統才能實現遠程掛載。
幸運的是,這樣的第三方系統多不勝舉,例如:基於網絡文件存儲的NFS、NAS;基於對象存儲的分佈式文件系統Ceph、MFS、Swift(不是ios的編程語言)、GlusterFS(高版本);以及ActiveMQ官方推薦的網絡塊存儲方案:SAN(就是成本有點高)。
我會在我另外一個專題——“系統存儲”中,和大家深入討論這些存儲方案在性能、維護、擴展性、可用性上的不同。爲了講解簡單,我們以下的講解採用NFS實現文件系統的共享。NFS技術比較成熟,在很多業務領域都有使用案例。如果您的業務生產環境還沒有達到滴滴、大衆點評、美團那樣對文件存儲性能上的要求,也可以將NFS用於生產環境。
1.2、實例參考
下面我們來演示兩個ActiveMQ節點建立在NFS網絡文件存儲上的 Master/Salve方案。關於怎麼安裝NFS軟件就不進行介紹了,畢竟本部分內容的核心還是消息服務中間件,不清楚NFS安裝的讀者可以自行百度/Google。
以下是我們演示環境中的IP位置和功能:
IP位置 | 作用 |
---|---|
192.168.61.140 | NFS文件服務 |
192.168.61.139 | 獨立的 ActiveMQ 節點 |
192.168.61.138 | 另一個獨立的 ActiveMQ 節點 |
1)首先爲兩個ActiveMQ節點掛載NFS服務:
-- 在140上設置的NFS共享路徑爲/usr/nfs 掛載到139和138的/mnt/mfdir/路徑下
-- 139和138上記得要安裝nfs-utils的客戶端模塊
mount 192.168.61.140:/usr/nfs /mnt/mfdir/
掛載後,可以通過df命令查詢掛載在後的結果:
從上圖中可以看到,192.168.61.140上提供的NFS共享目錄通過mount命令掛載成爲了138和139兩個物理機上的本地磁盤路徑。
2)然後更改138和139上ActiveMQ的主配置文件,主要目的是將使用的KahaDB/LevelDB的主路徑設置爲在共享文件系統的相同位置:
......
<persistenceAdapter>
<!--
這裏使用KahaDB,工作路徑設置在共享路徑的kahaDB文件夾下
138和139都設置爲相同的工作路徑
-->
<kahaDB directory="/mnt/mfdir/kahaDB"/>
</persistenceAdapter>
......
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
3)然後同時啓動138和139上的ActiveMQ服務節點。這時我們可以看到某個節點出現以下的提示信息(記得是通過console模式進行觀察):
......
jvm 1 | INFO | Using Persistence Adapter: KahaDBPersistenceAdapter[/mnt/mfdir/kahaDB]
jvm 1 | INFO | Database /mnt/mfdir/kahaDB/lock is locked by another server. This broker is now in slave mode waiting a lock to be acquired
......
- 1
- 2
- 3
- 4
在本文的演示環境中,出現以上提示的是工作在139上的ActiveMQ服務節點。這說明這個節點發現主工作路徑已經被其他ActiveMQ服務節點鎖定了,所以自動進入了Slave狀態。另外這還說明,另外運行在138物理機上的ActiveMQ服務搶佔到了主目錄的操作權。
接下來我們將工作在138上的ActiveMQ服務節點停止工作,這時139上的ActiveMQ Slave服務節點自動切換爲Master狀態:
......
jvm 1 | INFO | KahaDB is version 6
jvm 1 | INFO | Recovering from the journal @1:47632
jvm 1 | INFO | Recovery replayed 53 operations from the journal in 0.083 seconds.
jvm 1 | INFO | PListStore:[/usr/apache-activemq-5.13.1/bin/linux-x86-64/../../data/activemq2/tmp_storage] started
jvm 1 | INFO | Apache ActiveMQ 5.13.1 (activemq2, ID:vm2-46561-1461220298816-0:1) is starting
......
- 1
- 2
- 3
- 4
- 5
- 6
- 7
從以上的提示可以看到,139上的ActiveMQ節點在自己的內存區域恢復了KahaDB的索引信息,並切換爲Master狀態繼續工作。需要注意的是,在139上的ActiveMQ節點切換爲Master狀態後,就算之前138上的ActiveMQ節點重新恢復工作,後者也不會再獲得主目錄的操作權限,只能進入Salve狀態。
2、基於共享關係型數據庫的熱備方案
基於關係型數據庫的熱備方案它的工作原理實際上和基於共享文件系統的熱備方案相似:
首先使用關係型數據庫作爲ActiveMQ的持久化存儲方案時,在指定的數據庫中會有三張數據表:activemq_acks,activemq_lock,activemq_msgs(有的情況下您生成的數據表名會是大寫的,這是因爲數據庫自身設置的原因);
其中“activemq_lock”這張數據表記錄了當前對數據庫擁有操作權限的ActiveMQ服務的ID信息、Name信息。各個ActiveMQ服務節點從這張數據表識別當前哪一個節點是Master狀態;
當需要搭建熱備方案時,兩個或者更多的ActiveMQ服務節點共享同一個數據服務。首先搶佔到數據庫服務的ActiveMQ節點,會將數據庫中“activemq_lock”數據表的Master狀態標記爲自己,這樣其它ActiveMQ服務節點就會進入Salve狀態。
之前講述瞭如何進行ActiveMQ服務的數據庫存儲方案的配置,這裏就不再進行贅述。只需要將每個ActiveMQ服務節點的數據庫連接設置成相同的位置,即可完成該熱備方案的配置工作。
爲了便於各位讀者進行這種方案的配置實踐,這裏給出了關鍵的配置信息:實際上真的很簡單,一定要首先確保您的數據庫是可用的,並且每一個ActiveMQ節點都這樣配置:
......
<broker xmlns="http://activemq.apache.org/schema/core">
......
<persistenceAdapter>
<!-- 設置使用的數據源 -->
<jdbcPersistenceAdapter dataSource="#mysql-ds"/>
</persistenceAdapter>
......
</broker>
......
<!-- 一定要確保數據庫可用,且在ActiveMQ的lib目錄中有必要的jar文件 -->
<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://您的mysql連接url信息?relaxAutoCommit=true"/>
<property name="username" value="activemq"/>
<property name="password" value="activemq"/>
<property name="poolPreparedStatements" value="true"/>
</bean>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
3、LevelDB + Zookeeper的熱備方案
從ActiveMQ V5.9.0+ 版本開始,ActiveMQ爲使用者提供了一種新的Master/Salve熱備方案。這個方案中,我們可以讓每個節點都有自己獨立的LevelDB數據庫(不是像1小節那樣共享LevelDB的工作目錄),並且使用Zookeeper集羣控制多個ActiveMQ節點的工作狀態,完成Master/Salve狀態的切換。工作模式如下圖所示(摘自官網):
在這種新的工作模式下,Master節點和各個Salve節點通過Zookeeper進行工作狀態同步,即使某個Salve節點新加入也沒有問題。下面我們一起來看看如何使用LevelDB + Zookeeper的熱備方案。下表中是我們將要使用的IP位置和相關位置的工作任務:
IP位置 | 作用 |
---|---|
192.168.61.140 | 單節點狀態的zookeeper服務 |
192.168.61.139 | 獨立的 ActiveMQ 節點 |
192.168.61.138 | 另一個獨立的 ActiveMQ 節點 |
在這裏的演示實例中我們使用單節點的ZK工作狀態(但是正式生產環境中,建議至少有三個zookeeper節點)。
1)首先更改139和138上工作的ActiveMQ服務節點,讓它們使用獨立的LevelDB,並且都連接到zookeeper服務。
......
<!--
注意無論是master節點還是salve節點,它們的brokerName屬性都必須一致
否則activeMQ集羣就算連接到了zookeeper,也不會把他們當成一個Master/Salve組
-->
<broker xmlns="http://activemq.apache.org/schema/core" brokerName="activemq" dataDirectory="${activemq.data}">
......
<persistenceAdapter>
<replicatedLevelDB
directory="/usr/apache-activemq-5.13.1/data/levelDB"
replicas="1"
bind="tcp://0.0.0.0:61615"
zkAddress="192.168.61.140:2181"
zkPath="/activemq/leveldb"/>
</persistenceAdapter>
......
</broker>
......
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
我們介紹一下以上配置段落所使用的一些重要屬性。通過LevelDB + Zookeeper組建的熱備方案中肯定會有一個ActiveMQ節點充當Master節點,至於有多少個Salve節點就可以根據讀者所在團隊、項目、需求等因素來綜合考慮了。這些Master節點和Salve節點的主配置文件中設置的brokerName屬性必須一致,否則activeMQ集羣就算連接到了zookeeper,也不會把他們當成一個Master/Salve組。
directory屬性是LevelDB的基本設置,表示當前該節點使用的LevelDB所在的主工作目錄,由於每個節點都有其獨立運行的LevelDB,所以各個節點的directory屬性設置的目錄路徑可以不一樣。但是根據對正式環境的管理經驗,建議還是將每個節點的directory屬性設置成相同的目錄路徑,方便進行管理。
對於replicas屬性,官方給出的解釋如下:
The number of nodes that will exist in the cluster. At least (replicas/2)+1 nodes must be online to avoid service outage.(default:3)
這裏的“number of nodes”包括了Master節點和Salve節點的總和。換句話說,如果您的集羣中一共有3個ActiveMQ節點,且只允許最多有一個節點出現故障。那麼這裏的值可以設置爲2(當然設置爲3也行,因爲整型計算中 3 / 2 + 1 = 2)。但如果您將replicas屬性設置爲4,就代表不允許3個節點的任何一個節點出錯,因爲:(4 / 2) + 1 = 3,也就是說3個節點是本集羣能夠允許的最小節點數。
一旦zookeeper發現當前集羣中可工作的ActiveMQ節點數小於所謂的“At least (replicas/2)+1 nodes”,在ActiveMQ Master節點的日誌中就會給出提示:“Not enough cluster members connected to elect a master.”,然後整個集羣都會停止工作,直到有新的節點連入,並達到所規定的“At least (replicas/2)+1 nodes”數量。
bind屬性指明瞭當本節點成爲一個Master節點後,通過哪一個通訊位置進行和其它Salve節點的消息複製操作。注意這裏配置的監聽地址和端口不能在transportConnectors標籤中進行重複配置,否則節點在啓動時會報錯。
When this node becomes a master, it will bind the configured address and port to service the replication protocol. Using dynamic ports is also supported.
zkAddress屬性指明瞭連接的zookeeper服務節點所在的位置。在以上實例中由於我們只有一個zookeeper服務節點,所以只配置了一個位置。如果您有多個zookeeper服務節點,那麼請依次配置這些zookeeper服務節點的位置,並以“,”進行分隔:
zkAddress="zoo1.example.org:2181,zoo2.example.org:2181,zoo3.example.org:2181"
- 1
由於zookeeper服務使用樹形結構描述數據信息,zkPath屬性就是設置整個ActiveMQ 主/備方案集羣在zookeeper存儲數據信息的根路徑的位置。當然這個屬性有一個默認值“/default”,所以您也可以不進行設置。
- 在完成138和139兩個節點的ActiveMQ服務配置後,我們同時啓動這兩個節點(注意,爲了觀察ActiveMQ的日誌請使用console模式啓動)。在其中一個節點上,可能會出現以下日誌信息:
......
jvm 1 | INFO | Opening socket connection to server 192.168.61.140/192.168.61.140:2181
jvm 1 | INFO | Socket connection established to 192.168.61.140/192.168.61.140:2181, initiating session
jvm 1 | INFO | Session establishment complete on server 192.168.61.140/192.168.61.140:2181, sessionid = 0x1543b74a86e0002, negotiated timeout = 4000
jvm 1 | INFO | Not enough cluster members have reported their update positions yet.
jvm 1 | INFO | Promoted to master
jvm 1 | INFO | Using the pure java LevelDB implementation.
jvm 1 | INFO | Apache ActiveMQ 5.13.1 (activemq, ID:vm2-45190-1461288559820-0:1) is starting
jvm 1 | INFO | Master started: tcp://activemq2:61615
......
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
從以上的日誌可以看到,這個節點連接上了zookeeper,並且分析了當前zookeeper上已連接的其它節點狀態後(實際上這個時候,還沒有其它節點進行連接),將自己“提升爲Master”狀態。在另外一個AcitveMQ的節點日誌中,讀者可以發現另一種形式的日誌提示,類似如下:
......
jvm 1 | INFO | Opening socket connection to server 192.168.61.140/192.168.61.140:2181
jvm 1 | INFO | Socket connection established to 192.168.61.140/192.168.61.140:2181, initiating session
jvm 1 | INFO | Session establishment complete on server 192.168.61.140/192.168.61.140:2181, sessionid = 0x1543b74a86e0005, negotiated timeout = 4000
jvm 1 | INFO | Slave started
......
- 1
- 2
- 3
- 4
- 5
- 6
從日誌中可以看到,這個節點成爲了一個Slave狀態的節點。
- 接下來我們嘗試停止當前Master節點的工作,並且觀察當前Salve節點的狀態變化。注意,如上文所述,replicas屬性的值一定要進行正確的設置:如果當Master節點停止後,當前還處於活動狀態的節點總和小於“(replicas/2)+1”,那麼整個集羣都會停止工作!
......
jvm 1 | INFO | Not enough cluster members have reported their update positio ns yet.
jvm 1 | INFO | Slave stopped
jvm 1 | INFO | Not enough cluster members have reported their update positio ns yet.
jvm 1 | INFO | Promoted to master
jvm 1 | INFO | Using the pure java LevelDB implementation.
jvm 1 | Replaying recovery log: 5.364780% done (956,822/17,835,250 bytes) @ 1 03,128.23 kb/s, 0 secs remaining.
jvm 1 | Replaying recovery log: 9.159451% done (1,633,611/17,835,250 bytes) @ 655.68 kb/s, 24 secs remaining.
jvm 1 | Replaying recovery log: 23.544615% done (4,199,241/17,835,250 bytes) @ 2,257.21 kb/s, 6 secs remaining.
jvm 1 | Replaying recovery log: 89.545681% done (15,970,696/17,835,250 bytes) @ 11,484.08 kb/s, 0 secs remaining.
jvm 1 | Replaying recovery log: 100% done
jvm 1 | INFO | Master started: tcp://activemq1:61615
......
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
從以上的日誌片段可以看到,Salve節點接替了之前Master節點的工作狀態,並恢復之前已同步的LevelDB文件到本節點的本地內存中,繼續進行工作。
我們在這篇文章做的演示只有兩個ActiveMQ服務節點和一個Zookeeper服務節點,主要是爲了向讀者介紹ActiveMQ下 LevelDB + Zookeeper的高可用方案的配置和切換過程,說明ActiveMQ高可用方案的重要性。實際生產環境下,這樣的節點數量配置會顯得很單薄,特別是zookeeper服務節點只有一個的情況下是不能保證整個集羣穩定工作的。正式環境下, 建議至少使用三個zookeeper服務節點和三個ActiveMQ服務節點,並將replicas屬性設置爲2。
4、ActiveMQ客戶端的故障轉移
以上三種熱備方案,都已向各位讀者介紹。細心的讀者會發現一個問題,因爲我們沒有使用類似Keepalived那樣的第三方軟件支持浮動IP。那麼無論以上三種熱備方案的哪一種,雖然服務端可以無縫切換提供連續的服務,但是對於客戶端來說連接服務器的IP都會發生變化。也就是說客戶端都會因爲連接異常脫離正常工作狀態。
爲了解決這個問題,AcitveMQ的客戶端連接提供了配套的解決辦法:連接故障轉移。開發人員可以預先設置多個可能進行連接的IP位置(這些位置不一定同時都是可用的),ActiveMQ的客戶端會從這些連接位置選擇其中一個進行連接,當連接失敗時自動切換到另一個位置連接。使用方式類似如下:
......
//這樣的設置,即使在發送/接收消息的過程中出現問題,客戶端連接也會進行自動切換
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("failover:(tcp://192.168.61.138:61616,tcp://192.168.61.139:61616)");
......
- 1
- 2
- 3
- 4
這樣的連接設置下,客戶端的連接就可以隨服務端活動節點的切換完成相應的轉換。
5、形成生產環境方案
ActiveMQ中主要的高性能、高可用方案到此就爲各位讀者介紹完了。可以看到在ActiveMQ單個節點性能配置已優化的前提下,ActiveMQ集羣的高性能方案可能會出現節點失效消息服務停止的情況;而ActiveMQ集羣的高可用性方案中,由於一次只有一個節點是Master狀態可以提供消息服務外,其他Salve節點都不能提供服務,所以並不能提高整個ActiveMQ集羣的性能。
因爲兩種方案都有其限制因素,所以在實際工作中將ActiveMQ應用到生產環境時,除非您的業務環境有特殊要求的情況,一般建議將ActiveMQ的高性能方案和高可用方案進行結合。以下向各位讀者提供一種ActiveMQ高性能和高可用性結合的方案:
將9個ActiveMQ節點分爲三組(brokerName1、brokerName2、brokerName3),每組都有三個ActiveMQ服務節點。另外準備三個節點的zookeeper服務集羣,所有三個組九個ActiveMQ服務都共享這三個zookeeper服務節點(只是每組ActiveMQ所設置的zkPath屬性不一樣);
將每組的三個ActiveMQ服務節點做LevelDB + Zookeeper的熱備方案(且設置replicas=2)。保證每組只有一個節點在一個時間內爲Master狀態。這樣整個集羣中的九個ActiveMQ服務節點就同時會有三個ActiveMQ服務節點處於Master狀態;
將整個集羣中所有ActiveMQ服務節點的高性能方案設置爲“組播發現”,並都採用一個相同的組播地址(可以採用默認的組播地址)。這樣三個處於Master狀態的ActiveMQ服務節點就會形成一個高性能方案(處於Salve狀態的節點不會發出組播消息)。整個設計結構如下圖所示: