RabbitMQ 高可用之映象佇列

架構師springboot發表於2018-12-29

 如果RabbitMQ叢集只有一個broker節點,那麼該節點的失效將導致整個服務臨時性的不可用,並且可能會導致message的丟失(尤其是在非持久化message儲存於非持久化queue中的時候)。可以將所有message都設定為持久化,並且使用持久化的queue,但是這樣仍然無法避免由於快取導致的問題:因為message在傳送之後和被寫入磁碟並執行fsync之間存在一個雖然短暫但是會產生問題的時間窗。通過publisher的confirm機制能夠確保客戶端知道哪些message已經存入磁碟,儘管如此,一般不希望遇到因單點故障導致服務不可用。

如果RabbitMQ叢集是由多個broker節點構成的,那麼從服務的整體可用性上來講,該叢集對於單點失效是有彈性的,但是同時也需要注意:儘管exchange和binding能夠在單點失效問題上倖免於難,但是queue和其上持有的message卻不行,這是因為queue及其內容僅僅儲存於單個節點之上,所以一個節點的失效表現為其對應的queue不可用。

舉例說明一下,如果一個MQ叢集由三個節點組成(MQ叢集節點的模式也是有講究的,一般三個節點會有一個RAM,兩個DISK),exchange、bindings 等後設資料會在三個節點之間同步,但queue上的訊息是不會同步的,且不特殊設定的情況下,Queue只會在一個節點存在。可能有的同學會提另一個問題,我從三個MQ幾點的監控皮膚,都可以看到這個Queue?這個是對的,這是由於Queue的後設資料也是在三個節點之間同步,但Queue的實際儲存只會在一個節點。我們傳送訊息到指定Queue,其實是傳送訊息到指定節點下的Queue。如下圖所示,訊息傳送至佇列testQueue,無論傳送者通過哪個MQ節點執行傳送,其最終的執行都會是在MQ03節點執行訊息的儲存。

說到這兒,可能有的小夥伴就要問了?說好的,RabbitMQ叢集提供高可用性呢。

分析一下,RabbitMQ叢集搭建完成後,如果不進行任何高可用配置,會有哪些問題呢?

  1. 單點故障會導致訊息丟失:如果MQ03節點故障,那麼MQ03 中的訊息就會丟失
  2. 無法最大化的利用MQ提供,提升執行效率:既然每次傳送到佇列testQueue的訊息都會在MQ03節點儲存,那麼何必搭建叢集。

引入RabbitMQ的映象佇列機制,將queue映象到cluster中其他的節點之上。在該實現下,如果叢集中的一個節點失效了,queue能自動地切換到映象中的另一個節點以保證服務的可用性。在通常的用法中,針對每一個映象佇列都包含一個master和多個slave,分別對應於不同的節點。slave會準確地按照master執行命令的順序進行命令執行,故slave與master上維護的狀態應該是相同的。除了publish外所有動作都只會向master傳送,然後由master將命令執行的結果廣播給slave們,故看似從映象佇列中的消費操作實際上是在master上執行的。
一旦完成了選中的slave被提升為master的動作,傳送到映象佇列的message將不會再丟失:publish到映象佇列的所有訊息總是被直接publish到master和所有的slave之上。這樣一旦master失效了,message仍然可以繼續傳送到其他slave上。

簡單來說,映象佇列機制就是將佇列在三個節點之間設定主從關係,訊息會在三個節點之間進行自動同步,且如果其中一個節點不可用,並不會導致訊息丟失或服務不可用的情況,提升MQ叢集的整體高可用性。

先來看下設定映象佇列後的效果: 映象佇列會出現+2標識。

1.設定佇列為映象佇列:How

兩種方式:

  1. 通過監控皮膚設定

  2. 通過命令設定
rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]

-p Vhost: 可選引數,針對指定vhost下的queue進行設定
Name: policy的名稱
Pattern: queue的匹配模式(正規表示式)
Definition:映象定義,包括三個部分ha-mode, ha-params, ha-sync-mode
        ha-mode:指明映象佇列的模式,有效值為 all/exactly/nodes
        all:表示在叢集中所有的節點上進行映象
        exactly:表示在指定個數的節點上進行映象,節點的個數由ha-params指定
        nodes:表示在指定的節點上進行映象,節點名稱通過ha-params指定
        ha-params:ha-mode模式需要用到的引數
        ha-sync-mode:進行佇列中訊息的同步方式,有效值為automatic和manual
priority:可選引數,policy的優先順序複製程式碼

請注意一個事實,映象配置的pattern 採用的是正規表示式匹配,也就是說會匹配一組。

RabbitMQ叢集節點失效,MQ處理策略

如果某個slave失效了,系統處理做些記錄外幾乎啥都不做:master依舊是master,客戶端不需要採取任何行動,或者被通知slave失效。
如果master失效了,那麼slave中的一個必須被選中為master。被選中作為新的master的slave通常是最老的那個,因為最老的slave與前任master之間的同步狀態應該是最好的。然而,特殊情況下,如果存在沒有任何一個slave與master完全同步的情況,那麼前任master中未被同步的訊息將會丟失。

映象佇列訊息的同步:

將新節點加入已存在的映象佇列時,預設情況下ha-sync-mode=manual,映象佇列中的訊息不會主動同步到新節點,除非顯式呼叫同步命令。當呼叫同步命令後,佇列開始阻塞,無法對其進行操作,直到同步完畢。當ha-sync-mode=automatic時,新加入節點時會預設同步已知的映象佇列。由於同步過程的限制,所以不建議在生產的active佇列(有生產消費訊息)中操作。

rabbitmqctl list_queues name slave_pids synchronised_slave_pids   檢視那些slaves已經完成同步複製程式碼
rabbitmqctl sync_queue name    手動的方式同步一個queue複製程式碼
rabbitmqctl cancel_sync_queue name 取消某個queue的同步功能 複製程式碼

以上針對訊息同步的命令,均可以通過監控介面來進行操作,最終也是通過這些操作命令執行。

說明:

  1. 映象佇列不是負載均衡,映象佇列無法提升訊息的傳輸效率,或者更進一步說,由於映象佇列會在不同節點之間進行同步,會消耗訊息的傳輸效率。
  2. 對exclusive佇列設定映象並不會有任何作用,因為exclusive佇列是連線獨佔的,當連線斷開,佇列自動刪除。所以實際上這兩個引數對exclusive佇列沒有意義。那麼有哪些佇列是exclusive呢?一般來說,釋出訂閱佇列及設定了該引數的佇列都是exclusive 排他性佇列。 如何確定一個佇列是不是排他性佇列呢? 如果佇列的features包含Excl,就代表它是排他性佇列。

映象佇列中某個節點宕掉的後果:

當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通知。如果未指定x-cancal-on-ha-failover引數,那麼消費者就無法感知master當機,會一直等待下去。
這就告訴我們,叢集中存在映象佇列時,重新master節點有風險。

映象佇列中節點啟動順序,非常有講究:

假設叢集中包含兩個節點,一般生產環境會部署三個節點,但為了方便說明,採用兩個節點的形式進行說明。

場景1:A先停,B後停
該場景下B是master,只要先啟動B,再啟動A即可。或者先啟動A,再在30s之內啟動B即可恢復映象佇列。(如果沒有在30s內回覆B,那麼A自己就停掉自己

場景2:A,B同時停
該場景下可能是由掉電等原因造成,只需在30s內聯絡啟動A和B即可恢復映象佇列。

場景3:A先停,B後停,且A無法恢復。
因為B是master,所以等B起來後,在B節點上呼叫rabbitmqctl forget_cluster_node A以接觸A的cluster關係,再將新的slave節點加入B即可重新恢復映象佇列。

場景4:A先停,B後停,且B無法恢復
該場景比較難處理,舊版本的RabbitMQ沒有有效的解決辦法,在現在的版本中,因為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的磁碟檔案
這個場景更加難以處理。將A或B的資料庫檔案($RabbitMQ_HOME/var/lib目錄中)copy至新節點C的目錄下,再將C的hostname改成A或者B的hostname。如果copy過來的是A節點磁碟檔案,按場景4處理,如果拷貝過來的是B節點的磁碟檔案,按場景3處理。最後將新的slave節點加入C即可重新恢復映象佇列。

場景6:A先停,B後停,且A和B均無法恢復,且無法得到A和B的磁碟檔案
無解。

啟動順序中有一個30s 的概念,這個是MQ 的時間間隔,用於檢測master、slave是否可用,因此30s 非常關鍵。

對於生產環境MQ叢集的重啟操作,需要分析具體的操作順序,不可無序的重啟,會有可能帶來無法彌補的傷害(資料丟失、節點無法啟動)。

簡單總結下:映象佇列是用於節點之間同步訊息的機制,避免某個節點當機而導致的服務不可用或訊息丟失,且針對排他性佇列設定是無效的。另外很重要的一點,映象佇列機制不是負載均衡。

歡迎工作一到五年的Java工程師朋友們加入Java架構開發:855801563 獲取更多免費視訊教程。

合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!!


相關文章