1. 程式人生 > >ActiveMQ的叢集與高可用

ActiveMQ的叢集與高可用

單純根據《ActiveMQ In Action(Manning-2011)》一書介紹的總結,部分介紹可能已經和官網不一。

一、ActiveMQ的高可用性

ActiveMQ使用master-slave模式實現高可用性,提供兩種實現主從模式的配置:shared nothing、shared storage(a relational database and a shared file system)

1.shared nothing master-slave

A shared nothing master-slave ActiveMQ broker deployment

每一個broker(包括master和slave)都有自己的訊息儲存區,這是最簡單的高可用性實現的辦法。
master複製所有的訊息指令給slave,複製的動作發生在master回覆client訊息已接收之前。
slave broker會在啟動的時候去連線master,所以理想上,master broker應該先啟動,slave broker 不會開啟任何transports,也就是說,slave broker不接收任何client請求和網路連線,除非master掛掉。slave通過檢測它與master之間的連線失敗而判定master掛掉。

shared nothing master-slave模式的處理過程:當一個生產者傳送一個持久化訊息到master之後,master會複製該訊息給slave,再返回接收應答給生產者,生產者才能傳送下一個訊息。

當master broker掛掉後,slave有兩個選擇:
1.關掉自己,因此,它只會儲存master的狀態。
2.開啟transports並且初始化所有的network connections,因此,該slave自動成為新的master。

如果slave broker成為新的master broker,所有的client可以通過failover機制去連線上新的master。在預設的client連線中,failover傳輸機制皆可以連線到master和slave:
failover://(tcp://masterhost:61616,tcp://slavehost:61616)?randomize=false

不過,使用shared nothing master-slave也有限制,如果client先連上master進行工作,而slave還沒與master進行連線,master掛掉,訊息很可能會丟失。ActiveMQ提供了一個waitForSlave屬性去設定master broker,強制master如果還沒與slave建立好連線,那麼不會接受任何client的連線,另一個限制是,一個master只允許有一個slave。

配置shared nothing master-slave

配置一個broker成為slave很簡單,配置一個masterConnector service:

<services
>
<!-- remoteURI:master broker的監聽地址 userName:Optional,如果master需要身份驗證 password:Optional,如果master需要身份驗證 --> <masterConnector remoteURI="tcp://remotehost:62001" userName="" password=""/> </services>

2.shared storage master-slave

share nothing master-slave模式下,每一個broker都獨自維護自己的storage,而shared storage master-slave模式允許多個broker共享儲存,但同一個時刻只有一個broker是存活的。shared storage master-slave的好處在於,它確保了當master掛掉之後,無需手動干預去保持應用的完整性,另一個好處是,slave的數量不再有所限制。
share nothing master-slave模式的配置有兩種:a relational database和file system-based storage.

shared database master-slave

Using a shared relational database for ActiveMQ master-slave
當一個ActiveMQ broker使用關係型資料庫時,它持有表的鎖以確保沒有其他broker同時訪問這個資料庫。多個broker同時執行並嘗試去訪問資料庫時,只有第一個broker會連線成功並拿到鎖,其他隨後到來的broker會一直poll直到它可以獲得鎖為止。這些處於polling狀態的broker,被視為slave,它們不會開啟任何傳輸連線或者網路連線。
該配置中所有的broker可以使用同一份配置檔案,這使得activemq啟動起來簡單得多。

shared file system master-slave

Using a file system for ActiveMQ master-slave

它建議使用 KahaDB 訊息儲存,但是對於訊息儲存使用底層的共享的檔案系統。當KahaDB訊息儲存啟動時,它將嘗試獲取檔案鎖,以防止任何其他broker同時訪問基於檔案的訊息儲存。

二、ActiveMQ是如何在brokers之間傳遞訊息

ActiveMQ中有一個概念:networks of brokers,它指的是連線ActiveMQ的訊息代理在一起形成不同的拓撲結構。
接下來就是分析brokers是如何在一個network中發現彼此且如何配置broker使其在network中合作。

1.儲存和轉發(store and forward)

ActiveMQ的儲存和轉發概念意味著,訊息在通過network轉發到其他broker之前,總是被儲存在本地broker中,也就是說,如果一條訊息由於連線原因沒有被交付,比如說,正在重連,broker將能夠通過網路連線將未交付的訊息傳送到遠端broker。預設情況下,network僅以單向方式操作,如圖:
Passing message between ActiveMQ brokers using store and forward
當然,這並不是說network只能單向操作,如果想要雙向操作,同樣可以在遠端broker中配置一個network connector指向本地的broker,或者直接指定建立的network connector為雙向duplex。

當本地broker和遠端broker之間建立好一條network後,遠端broker會將其所有持久和處於活動的消費者的目的地資訊傳遞給本地broker,本地broker使用這些資訊去判斷遠端broker對哪種訊息感興趣,並轉發該型別訊息給它。

這裡,書中舉了一個場景,假如我們有多個超市需要連線到一個後臺辦公訂購系統,這將很難靈活擴充套件新的超市,後臺辦公訂購系統不好掌控所有新加入的超市即遠端broker。
這裡寫圖片描述
給個圖示,這裡想象超市和後臺之間有一個防火牆。(至於為什麼這麼想象,我並不得知)注意到這裡,超市broker和back office之間的network是雙向的,超市broker的配置:

<networkConnectors>
    <networkConnector uri="static://(tcp://backoffice:61617)" 
        name="bridge" 
        duplex="true"
        conduitSubscriptions="true"
        decreaseNetworkConsumerPriority="false">
    </networkConnector>
</networkConnectors>

這裡關於配置,主要注意一點是,配置的順序是很重要的,關於networks,persistence,transports的順序如下:

  1. Networks——必須在訊息儲存之前建立
  2. Message store——必須在傳輸配置好之前配置完
  3. Transports——必須在broker配置的最後

舉個例子:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://activemq.apache.org/schema/core">
    <broker brokerName="receiver" persistent="true" useJmx="true">
        <networkConnectors>
            <networkConnector uri="static:(tcp://backoffice:61617)"/>
        </networkConnectors>
        <persistenceAdapter>
            <kahaDB directory = "activemq-data"/>       
        </persistenceAdapter>
        <transportConnectors>
            <transportConnector uri="tcp://localhost:62002"/>
        </transportConnectors>
    </broker>
</beans>

來一張,在大型開發場景下的高可用性和network配置結合:
Combine high availability and networks for an enterprise deployment of ActiveMQ

2.Network發現機制

ActiveMQ提供兩種發現機制:

  1. Dynamic——使用組播和會合方式搜尋brokers
  2. Static——通過一個URI列表配置brokers

使用組播發現的方式去建立network連線是最簡單粗暴的,當你啟動一個broker時,它會通過組播IP去搜索其他的broker並建立network連線。配置方式如下:

<networkConnectors>
    <networkConnector uri="multicast://default"/>
</networkConnectors>

這裡“default”表示這個broker屬於哪個組,建議使用唯一的名字去標識,以免你的broker連線到其他你不知道的應用代理上。
組播發現機制有一些限制,比如說不能控制哪些broker被發現,事實上,它通常侷限於本地網段上去發現其他broker,因為組播IP不通過路由器延伸。
關於第二種方式,static,事實上,在這之前的配置一直都是static,只不過broker的URL列表有點少而已,

<networkConnectors>
    <networkConnector uri="static:(tcp://remote-master:61617,tcp://remote-slave:61617)"/>
</networkConnectors>

static的配置屬性:
static配置屬性表

3.Network配置

對於遠端broker現存在的目的地,可能沒有任何活動持久的訂閱者或消費者,因此,當network初始化連線到遠端broker時,遠端broker會讀取它現存目的地的訊息,並傳遞給本地broker,然後,本地broker也可以轉發那些目的地的訊息。
重要的是要注意,一個network將使用broker的名稱來代表遠端broker建立唯一的持久預訂代理。 因此,如果在稍後的時間點更改broker的名稱,很可能會通過network丟失持久主題訂閱者的訊息。 為避免這種情況,請確保為元素上的brokerName屬性使用唯一的名稱。 有關簡要示例,請參閱以下內容:

<broker xmlns="http://activemq.apache.org/schema/core/"
    brokerName="brokerA"
    dataDirectory="${activemq.base}/data">
...
    <networkConnectors>
        <networkConnector name="brokerA to brokerB" uri="tcp://remotehost:61616"/>
    </networkConnectors>
</broker>

關於Network配置還有很多,不一一列舉了。

三、為大量併發應用程式部署ActiveMQ

擴充套件使用ActiveMQ的應用程式可能需要一些時間,需要一些努力。 在本節中,將介紹三種技術來幫助完成此任務。首先是垂直擴充套件,單個broker用於數千個連線和佇列。然後將通過使用network水平擴充套件應用程式來擴充套件到數萬個連線。 最後,將研究流量分割槽,這將平衡擴充套件和效能,但會增加ActiveMQ應用程式的複雜性。

1.垂直擴充套件

垂直擴充套件是一種用於增加單個ActiveMQ broker可以處理的連線數(因此增加負載)的技術。預設情況下,ActiveMQ broker設計為儘可能高效地移動訊息,以確保低延遲和良好的效能。但是我們可以做一些配置調整,以確保ActiveMQ broker可以處理大量的併發連線和大量的佇列。

預設情況下,ActiveMQ將使用阻塞I/O來處理傳輸連線。 這導致每個連線使用一個執行緒。 我們可以在ActiveMQ broker上使用非阻塞I/O(而客戶端上仍然使用預設傳輸)來減少使用的執行緒數。broker的非阻塞I/O配置如下:

<broker>
    <transportConnectors>
        <transportConnector name="nio" uri="nio://localhost:61616"/>
    </transportConnectors>
</broker>

除了每個連線使用一個執行緒來阻塞I/O外,ActiveMQ broker可以使用執行緒為每個客戶端連線分派訊息。可以通過將名為org.apache.activemq.UseDedicatedTaskRunner的系統屬性設定為false,讓ActiveMQ使用執行緒池。

ACTIVEMQ_OPTS="-Dorg.apache.activemq.UseDedicatedTaskRunner=false"

確保ActiveMQ broker具有足夠的記憶體來處理大量併發連線有兩步過程。
首先,需要確保啟動ActiveMQ broker的JVM配置了足夠的記憶體。

ACTIVEMQ_OPTS="-Xmx1024M \-Dorg.apache.activemq.UseDedicatedTaskRunner=false"

第二,確保專門為ActiveMQ broker在JVM配置適當的記憶體量。此調整通過< system-Usage >元素的limit屬性進行。(最好從512MB開始,如果測試不夠再往上加),配置示例:

<systemUsage>
    <systemUsage>
        <memoryUsage>
            <memoryUsage limit="512 mb"/>
        </memoryUsage>
        <storeUsage>
            <storeUsage limit="10 gb" name="foo"/>
        </storeUsage>
        <tempUsage>
            <tempUsage limit="1 gb"/>
        </tempUsage>
    </systemUsage>
</systemUsage>

還應該降低每一個連線的CPU負載,如果使用的OpenWire連線方式,禁用緊密編碼,否則會使得CPU過度緊張。

String uri = "failover://(tcp://localhost:61616?" + " wireFormat.tightEncodingEnabled=false)";
ConnectionFactory cf = new ActiveMQConnectionFactory(uri);

前面研究的是broker怎麼調整去處理數千個連線,下面開始研究的是怎麼調整broker去處理數千個佇列。

預設佇列配置使用單獨的執行緒來將訊息從訊息儲存區分頁到佇列中,以便分發給感興趣的訊息消費者。 對於大量佇列,建議通過為所有佇列啟用optimize-Dispatch屬性來禁用此功能,

<destinationPolicy>
    <policyMap>
        <policyEntries>
            <policyEntry queue=">" optimizedDispatch="true"/>
        </policyEntries>
    </policyMap>
</destinationPolicy>

為了確保不僅可以擴充套件到數千個連線,而且還可以擴充套件到數萬個佇列,使用JDBC訊息儲存庫或更新和更快的KahaDB訊息儲存庫。 KahaDB預設情況下在ActiveMQ中啟用。

到目前為止,我們已經考慮了擴充套件連線,減少執行緒使用,並選擇正確的訊息儲存。 調整用於擴充套件的ActiveMQ的示例配置如以下:

<broker xmlns="http://activemq.apache.org/schema/core" brokerName="amq-broker" dataDirectory="${activemq.base}/data">
    <persistenceAdapter>
        <kahaDB directory="${activemq.base}/data" journalMaxFileLength="32mb"/>
    </persistenceAdapter>
    <destinationPolicy>
        <policyMap>
            <policyEntries>
                <policyEntry queue="&gt;" optimizedDispatch="true"/>
            </policyEntries>
        </policyMap>
    </destinationPolicy>
    <systemUsage>
        <systemUsage>
            <memoryUsage>
                <memoryUsage limit="512 mb"/>
            </memoryUsage>
            <storeUsage>
                <storeUsage limit="10 gb" name="foo"/>
            </storeUsage>
            <tempUsage>
                <tempUsage limit="1 gb"/>
            </tempUsage>
        </systemUsage>
    </systemUsage>
    <transportConnectors>
        <transportConnector name="openwire" uri="nio://localhost:61616"/>
    </transportConnectors>
</broker>

2.水平擴充套件

除了擴充套件單個broker之外,還可以使用networks來增加可用於應用程式的ActiveMQ broker的數量。 由於networks會自動將訊息傳遞給具有感興趣的消費者的連線broker,因此可以將客戶端配置為連線到一個broker叢集,隨機選擇一個來連線。

failover://(tcp://broker1:61616,tcp://broker2:61616)?randomize=true

為了確保佇列或持久主題訂閱者的訊息不會在broker上孤立,需要將network配置為使用dynamicOnly和低網路prefetchSize

<networkConnector uri="static://(tcp://remotehost:61617)"
    name="bridge"
    dynamicOnly="true"
    prefetchSize="1">
</networkConnector>

使用network進行水平擴充套件會帶來更多的延遲,因為潛在的訊息必須在分發給消費者之前通過多個broker。

另一種替代部署提供了巨大的可擴充套件性和效能,但需要更多的應用規劃。 這種混合解決方案稱為流量分割槽。

3.流量分割槽

客戶端流量分割是垂直和水平分割的混合。 通常不使用network,因為客戶端應用程式決定什麼流量應該到哪個broker上。 客戶端應用程式必須維護多個JMS連線,並決定哪些JMS連線應用於哪些目標。
不直接使用network connection的優點是,減少在brokers之間轉發訊息的開銷。 需要平衡這與導致典型應用程式的額外複雜性。Fig10.8是流量分割槽的一個使用代表:
Using traffic partition with ActiveMQ master/slave deployment