本文的ActiveMQ都基於5.10版本,參考了ActiveMQ官方文檔:http://activemq.apache.org/failover-transport-reference.html。
集羣是個比較廣泛的概念,它有多種形式,關於消息服務的集羣,大概分爲Consumer集羣(消費者集羣)和Broker集羣(消息服務器集羣)兩種。
對於消費者集羣,對於隊列消費者,主要是:1.保證如果某一個消費者死亡了,任何它沒有確認完的消息會被重傳別的正常的消費者來消費;2.如果一個消費者消費消息過快,就可以比別的消費者得到更多的消息;3.如果一個消費者消費消息過慢,它就會被少得到消息。第1點幾乎是所有JMS提供者都有的功能——消息重傳機制(可以參考我的其他ActiveMQ博文)。第2點和第3點也是很正常的,因爲大多消費者和線程是一一對應的關係,你消費速率快,當然可以自己去服務器拉取更多的消息。當然ActiveMQ在隊列上給消費者提供了高性能的負載均衡策略。對於主題訂閱者,由於每個訂閱者接受到被推送的消息都和其他訂閱者無關,所以處理相對簡單,JMS也有持久訂閱者這一概念,這裏不多說。
對於消息服務器集羣,主要是指:1.如果集羣中的某一臺消息服務器宕機,與該臺消息服務器相連接的生產者和消費者能否自動連接到其他正常工作的消息服務器。2.如果集羣中的某一臺消息服務器宕機,該臺服務器上未消費的消息能否在該臺服務器恢復正常之前由其他服務器轉發。3.集羣環境中會不會導致某臺消息服務器上只有消費者或者某臺消息服務器上只有生產者。對於1,ActiveMQ提供了一種叫做失效轉移(也叫故障轉移,FailOver)的策略。失效轉移提供了在傳輸層上重新連接到其他任何傳輸器的功能。使用它很簡單,只需要在uri中配置就行了,語法如下:
failover:(uri1,...,uriN)?transportOptions 或者 failover:uri1,...,uriN
例子:
failover:(tcp://primary:61616,tcp://secondary:61616)?randomize=false
failover:(tcp://localhost:61616,tcp://remotehost:61616)?initialReconnectDelay=100 (
如果這樣使用報錯你可以試試這個:failover://(tcp://localhost:61616,tcp://remotehost:61616)?initialReconnectDelay=100 (this way works in ActiveMQ 4.1.1 the one above does not)
)如果某個ActiveMQ客戶端發現uri1地址失效了,它會立即轉向uri地址列表中其他可以連接的消息服務器進行重連,以保證繼續正常工作,請注意,並不是uri1失效了就會選則uri2重連,這種選擇其他地址的方式默認是隨機的,以保證負載均衡,如果你想關閉隨機,可以transportOptions中加入randomize=false。
transportOptions有多種參數可以選擇,如下:
initialReconnectDelay:默認爲10,單位毫秒,表示第一次嘗試重連之前等待的時間。
maxReconnectDelay:默認30000,單位毫秒,表示兩次重連之間的最大時間間隔。
useExponentialBackOff:默認爲true,表示重連時是否加入避讓指數來避免高併發。
reconnectDelayExponent:默認爲2.0,重連時使用的避讓指數。
maxReconnectAttempts:5.6版本之前默認爲-1,5.6版本及其以後,默認爲0,0表示重連的次數無限,配置大於0可以指定最大重連次數。
startupMaxReconnectAttempts:默認爲0,如果該值不爲0,表示客戶端接收到消息服務器發送來的錯誤消息之前嘗試連接服務器的最大次數,一旦成功連接後,maxReconnectAttempts值開始生效,如果該值爲0,則默認採用maxReconnectAttempts。詳見FailoverTransport.java代碼:
private int calculateReconnectAttemptLimit() {
int maxReconnectValue = this.maxReconnectAttempts;
if (firstConnection && this.startupMaxReconnectAttempts != INFINITE) {
maxReconnectValue = this.startupMaxReconnectAttempts;
}
return maxReconnectValue;
}
randomize:默認爲true,表示在URI列表中選擇URI連接時是否採用隨機策略,記住,這種隨機策略在第一次選擇URI列表中的地址時就開始生效,所以,如果爲true的話,一個生產者和一個消費者的Failover連接地址都是兩個URI的話,有可能生產者連接的是第一個,而消費者連接的是第二個,造成一個服務器上只有生產者,一個服務器上只有消費者的尷尬境地。
backup:默認爲false,表示是否在連接初始化時將URI列表中的所有地址都初始化連接,以便快速的失效轉移,默認是不開啓。
timeout:默認爲-1,單位毫秒,是否允許在重連過程中設置超時時間來中斷的正在阻塞的發送操作。-1表示不允許,其他表示超時時間。這樣說你肯定不是很明白,直接看代碼吧,下面給出FailoverTransport.java類中oneway方法中的一段代碼給你看你就明白了:
// Keep trying until the message is sent.
for (int i = 0; !disposed; i++) {
try {
// Wait for transport to be connected.
Transport transport = connectedTransport.get();
long start = System.currentTimeMillis();
boolean timedout = false;
while (transport == null && !disposed && connectionFailure == null
&& !Thread.currentThread().isInterrupted()) {
if (LOG.isTraceEnabled()) {
LOG.trace("Waiting for transport to reconnect..: " + command);
}
long end = System.currentTimeMillis();
if (command.isMessage() && timeout > 0 && (end - start > timeout)) {
timedout = true;
if (LOG.isInfoEnabled()) {
LOG.info("Failover timed out after " + (end - start) + "ms");
}
break;
}
try {
reconnectMutex.wait(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
if (LOG.isDebugEnabled()) {
LOG.debug("Interupted: " + e, e);
}
}
transport = connectedTransport.get();
}
// 其餘的代碼略
trackMessages:默認值爲false,是否緩存在發送中(in-flight messages)的消息,以便重連時讓新的Transport繼續發送。默認是不開啓。
maxCacheSize:默認131072,如果trackMessages爲true,該值表示緩存消息的最大尺寸,單位byte。
updateURIsSupported:默認值爲true,表示重連時客戶端新的連接器(Transport)是否從消息服務接受接受原來的URI列表的更新,5.4及其以後的版本可用。如果關閉的話,會導致重連後連接器沒有其他的URI地址可以Failover。
updateURIsURL:默認爲null,從5.4及其以後的版本,ActiveMQ支持從文件中加載Failover的URI地址列表,URI還是以逗號分隔,updateURIsURL爲文件路徑。詳見FailoverTransport.java中代碼:
private void doUpdateURIsFromDisk() {
// If updateURIsURL is specified, read the file and add any new
// transport URI's to this FailOverTransport.
// Note: Could track file timestamp to avoid unnecessary reading.
String fileURL = getUpdateURIsURL();
if (fileURL != null) {
BufferedReader in = null;
String newUris = null;
StringBuffer buffer = new StringBuffer();
try {
in = new BufferedReader(getURLStream(fileURL));
while (true) {
String line = in.readLine();
if (line == null) {
break;
}
buffer.append(line);
}
newUris = buffer.toString();
} catch (IOException ioe) {
LOG.error("Failed to read updateURIsURL: " + fileURL, ioe);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ioe) {
// ignore
}
}
}
processNewTransports(isRebalanceUpdateURIs(), newUris);
}
}
nested.*:默認爲null,5.9及其以後版本可用,表示給嵌套的URL添加額外的選項。 以前,如果你想檢測讓死連接速度更快,你必須在wireFormat.maxInactivityDuration= 1000選項添加到失效轉移列表中的所有嵌套的URL。例如:
failover:(tcp://host01:61616?wireFormat.maxInactivityDuration=1000,tcp://host02:61616?wireFormat.maxInactivityDuration=1000,tcp://host03:61616?wireFormat.maxInactivityDuration=1000)
而現在,你只需要這樣:
failover:(tcp://host01:61616,tcp://host02:61616,tcp://host03:61616)?nested.wireFormat.maxInactivityDuration=1000
warnAfterReconnectAttempts.*:默認爲10,5.10及其以後的版本可用,表示每次重連該次數後會打印日誌告警,設置<=0的值表示禁用,FailoverTransport.java類的doReconnect()部分相關代碼如下
int warnInterval = getWarnAfterReconnectAttempts();
if (warnInterval > 0 && (connectFailures % warnInterval) == 0) {
LOG.warn("Failed to connect to {} after: {} attempt(s) continuing to retry.",
uris, connectFailures);
}
reconnectSupported:默認爲true,表示客戶端是否應響應經紀人
ConnectionControl事件與重新連接(參見:rebalanceClusterClients)。
如果你使用Failover失效轉移,則消息服務器在死亡的那一刻,你的生產者發送消息時默認將阻塞,但你可以設置發送消息阻塞的超時時間(注:timeout參數前面已經講過了):
failover:(tcp://primary:61616)?timeout=3000
上面的設置將導致如果3秒後連接還未建立,將導致消息發送失敗,但這並不會導致該連接被kill,所以你可以過一陣子後再使用這一個連接來嘗試發送消息。
如果用戶希望能追蹤到重連過程,可以在ActiveMQConnectionFactory設置一個TransportListener,如下所示:
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("failover:(tcp://localhost:61616,tcp://localhost:61617)?randomize=false");
factory.setTransportListener(new TransportListener() {
@Override
public void transportResumed() {
System.out.println("連接器已經恢復完畢!");
}
@Override
public void transportInterupted() {
System.out.println("連接器被中斷了!");
}
@Override
public void onException(IOException error) {
System.out.println(error);
}
@Override
public void onCommand(Object command) {
System.out.println(command);
}
});
下面我們看看如何在消息服務器(broker)上配置失效轉移Failover。
在消息服務器這邊,有一些選項可以將客戶端更新到新的消息服務器,如下所示:
updateClusterClients:默認爲false,如果爲true,則會將broker集羣的拓撲結構的改變信息傳遞給連接的客戶端。
rebalanceClusterClients:默認爲false,如果爲true,則如果有新的消息服務器加入到消息服務器集羣中,則連接的客戶端將被要求重新平衡(asked to rebalance)。注意, priorityBackup=true能覆蓋。
updateClusterClientsOnRemove:默認爲false,如果爲true,則當一個集羣從網絡中移除的時候將更新客戶端。有了這個選項,可以在消息服務器移除時更新客戶端,而不是僅僅只是新增消息服務器時更新。(難道官方文檔有問題:if true, will update clients when a cluster is removed from the network. Having this as separate option enables clients to be updated when new brokers join, but not when brokers leave.)
updateClusterFilter:默認爲null,如果有值,將會是逗號分隔的正則表達式列表,用來過濾掉失效轉移時的消息服務器集羣中的服務器名稱。
舉例:
<broker>
...
<transportConnectors>
<transportConnector name="openwire" uri="tcp://0.0.0.0:61616" updateClusterClients="true"
updateClusterFilter="*A*,*B*" />
</<transportConnectors>
...
</broker>
如果面配置所示,如果updateClusterClients設置爲true,則連接到該服務器的客戶端的連接器地址Failover的URI列表只需要寫這個服務器地址就行,如:failover://tcp://primary:61616,當有新的消息服務器加入時,這些客戶端將自動被更新添加該新消息服務器的地址,如果發生網絡或消息服務器宕機的事件,就可以重連接到新的消息服務器上。
有時候我們希望客戶端能優先選擇某些消息服務器地址,比如既有本地服務器,又有遠程服務器,我們希望本地的應用程序優先選擇本地服務器進行連接,從5.6版本開始,ActiveMQ提供了優先級備份(priority backup )的特性,所以你可以讓客戶端自動重連到所謂的“優先級”URI,你可以在客戶端如下配置URI地址:
failover:(tcp://local:61616,tcp://remote:61616)?randomize=false&priorityBackup=true
如果上面這個地址被用於客戶端使用,客戶端將嘗試並保持連接到本地(上面local表示的地址)的消息服務器,當然,如果本地的服務器故障,將轉移到遠程(remote代表的地址)服務器。默認情況下,只有URI列表中的第一個被視爲優先(本地)URI,在某些情況下,你希望不止一個URI地址優先,則你可以使用priorityURIs參數:
failover:(tcp://local1:61616,tcp://local2:61616,tcp://remote:61616)?randomize=false&priorityBackup=true&priorityURIs=tcp://local1:61616,tcp://local2:61616
這樣,客戶端將視local1和local2都爲優先URI。