RabbitMQ 高可用之鏡像隊列

如果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集群提供高可用性呢。

分析一下,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標識。

RabbitMQ 高可用之鏡像隊列

1.設置隊列為鏡像隊列:How

兩種方式:

通過監控面板設置

RabbitMQ 高可用之鏡像隊列

通過命令設置

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,就代表它是排他性隊列。
RabbitMQ 高可用之鏡像隊列

鏡像隊列中某個節點宕掉的後果:

當slave宕掉了,除了與slave相連的客戶端連接全部斷開之外,沒有其他影響。

當master宕掉時,會有以下連鎖反應:

1. 與master相連的客戶端連接全部斷開;

2.選舉最老的slave節點為master。若此時所有slave處於未同步狀態,則未同步部分消息丟失;

3.新的master節點requeue所有unack消息,在此我向大家推薦一個架構學習交流圈:830478757 幫助突破瓶頸 提升思維能力,因為這個新節點無法區分這些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集群的重啟操作,需要分析具體的操作順序,不可無序的重啟,會有可能帶來無法彌補的傷害(數據丟失、節點無法啟動)。

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


分享到:


相關文章: