RabbitMQ叢集故障恢復詳解
RabbitMQ的mirror queue(映象佇列)機制是最簡單的佇列HA方案,它通過在cluster的基礎上增加ha-mode、ha-param等policy選項,可以根據 需求將cluster中的佇列映象到多個節點上,從而實現高可用,消除cluster模式中佇列內容單點帶來的風險。
在使用映象佇列之前,有幾點注意事項必須熟記於心(下文中將混用主節點和master,從節點和slave):
1. 映象佇列不能作為負載均衡使用,因為每個操作在所有節點都要做一遍。
2. ha-mode引數和durable declare對exclusive佇列都不生效,因為exclusive佇列是連線獨佔的,當連線斷開,佇列自動刪除。所以實際上這兩個引數對exclusive佇列沒有意義。
3. 將新節點加入已存在的映象佇列時,預設情況下ha-sync-mode=manual,映象佇列中的訊息不會主動同步到新節點,除非顯式呼叫同步命令。當 呼叫同步命令(via rabbitmqctl or web-based ui)後,佇列開始阻塞,無法對其進行操作,直到同步完畢。當ha-sync-mode=automatic時,新加入節點時會默認同步已知的映象佇列。 由於同步過程的限制,所以不建議在生產環境的active佇列(有生產消費訊息)中操作。
4. 每當一個節點加入或者重新加入(例如從網路分割槽中恢復回來)映象佇列,之前儲存的佇列內容會被清空。
5. 映象佇列有主從之分,一個主節點(master),0個或多個從節點(slave)。當master宕掉後,會在slave中選舉新的master。選舉演算法為最早啟動的節點。
6. 當所有slave都處在(與master)未同步狀態時,並且ha-promote-on-shutdown policy設定為when-syned(預設)時,如果master因為主動的原因停掉,比如是通過rabbitmqctl stop命令停止或者優雅關閉OS,那麼slave不會接管master,也就是說此時映象佇列不可用;但是如果master因為被動原因停掉,比如VM 或者OS crash了,那麼slave會接管master。這個配置項隱含的價值取向是優先保證訊息可靠不丟失,放棄可用性。如果ha-promote-on- shutdown policy設定為alway,那麼不論master因為何種原因停止,slave都會接管master,優先保證可用性。
7. 映象佇列中最後一個停止的節點會是master,啟動順序必須是master先起,如果slave先起,它會有30秒的等待時間,等待master啟動, 然後加入cluster。當所有節點因故(斷電等)同時離線時,每個節點都認為自己不是最後一個停止的節點。要恢復映象佇列,可以嘗試在30秒之內同時啟 動所有節點。
8. 對於映象佇列,客戶端basic.publish操作會同步到所有節點;而其他操作則是通過master中轉,再由master將操作作用於salve。 比如一個basic.get操作,假如客戶端與slave建立了TCP連線,首先是slave將basic.get請求傳送至master,由 master備好資料,返回至slave,投遞給消費者。
9. 由8可知,當slave宕掉時,除了與slave相連的客戶端連線全部斷開之外,沒有其他影響。當master宕掉時,會有以下連鎖反應:1)與 master相連的客戶端連線全部斷開。2)選舉最老的slave為master。若此時所有slave處於未同步狀態,則未同步部分訊息丟失。3)新的 master節點requeue所有unack訊息,因為這個新節點無法區分這些unack訊息是否已經到達客戶端,亦或是ack訊息丟失在到老 master的通路上,亦或是丟在老master組播ack訊息到所有slave的通路上。所以處於訊息可靠性的考慮,requeue所有unack的消 息。此時客戶端可能受到重複訊息。4)如果客戶端連著slave,並且basic.consume訊息時指定了x-cancel-on-ha- failover引數,那麼客戶端會收到一個Consumer Cancellation Notification通知,Java SDK中會回撥Consumer介面的handleCancel()方法,故需覆蓋此方法。如果不指定x-cancel-on-ha-failover參 數,那麼消費者就無法感知master宕機,會一直等待下去。
上面列出的注意事項整理自官方的HA文件。
下面的映象佇列恢復才是本文重點:
* 前提:兩個節點(A和B)組成一個映象佇列。
* 場景1:A先停,B後停。
該場景下B是master,只要先啟動B,再啟動A即可。或者先啟動A,再在30秒之內啟動B即可恢復映象佇列。
* 場景2: A, B同時停。
該場景可能是由掉電等原因造成,只需在30秒之內連續啟動A和B即可恢復映象佇列。
* 場景3:A先停,B後停,且A無法恢復。
該場景是場景1的加強版,因為B是master,所以等B起來後,在B節點上呼叫rabbitmqctl forget_cluster_node A,解除與A的cluster關係,再將新的slave節點加入B即可重新恢復映象佇列。* 場景4:A先停,B後停,且B無法恢復。
該場景是場景3的加強版,比較難處理,早在3.1.x時代之前貌似都沒什麼好的解決方法,可能是我不知道,但是現在已經有解決方法了,在3.4.2 版本親測有效。因為B是master,所以直接啟動A是不行的,當A無法啟動時,也就沒辦法在A節點上呼叫rabbitmqctl forget_cluster_node B了。新版本中,forget_cluster_node支 持–offline引數,offline引數允許rabbitmqctl在離線節點上執行forget_cluster_node命令,迫使 RabbitMQ在未啟動的slave節點中選擇一個作為master。當在A節點執行rabbitmqctl forget_cluster_node –offline B時,RabbitMQ會mock一個節點代表A,執行forget_cluster_node命令將B剔出cluster,然後A就能正常啟動了。最後 將新的slave節點加入A即可重新恢復映象佇列。* 場景5: A先停,B後停,且A、B均無法恢復,但是能得到A或B的磁碟檔案。
該場景是場景4的加強版,更加難處理。將A或B的資料庫檔案(預設在$RABBIT_HOME/var/lib目錄中)拷貝至新節點C的目錄下,再 將C的hostname改成A或B的hostname。如果拷過來的是A節點磁碟檔案,按場景4處理方式;如果拷過來的是B節點磁碟檔案,按場景3處理方 式。最後將新的slave節點加入C即可重新恢復映象佇列。
* 場景6:A先停,B後停,且A、B均無法恢復,且無法得到A或B的磁碟檔案。