如果RabbitMQ集群只有一個broker節(jié)點,那么該節(jié)點的失效將導致整個服務臨時性的不可用,并且可能會導致message的丟失(尤其是在非持久化message存儲于非持久化queue中的時候)??梢詫⑺衜essage都設置為持久化,并且使用持久化的queue,但是這樣仍然無法避免由于緩存導致的問題:因為message在發(fā)送之后和被寫入磁盤并執(zhí)行fsync之間存在一個雖然短暫但是會產(chǎn)生問題的時間窗。通過publisher的confirm機制能夠確??蛻舳酥滥男﹎essage已經(jīng)存入磁盤,盡管如此,一般不希望遇到因單點故障導致服務不可用。
? ? ?如果RabbitMQ集群是由多個broker節(jié)點構成的,那么從服務的整體可用性上來講,該集群對于單點失效是有彈性的,但是同時也需要注意:盡管exchange和binding能夠在單點失效問題上幸免于難,但是queue和其上持有的message卻不行,這是因為queue及其內容僅僅存儲于單個節(jié)點之上,所以一個節(jié)點的失效表現(xiàn)為其對應的queue不可用。
舉例說明一下,如果一個MQ集群由三個節(jié)點組成(MQ集群節(jié)點的模式也是有講究的,一般三個節(jié)點會有一個RAM,兩個DISK),exchange、bindings 等元數(shù)據(jù)會在三個節(jié)點之間同步,但queue上的消息是不會同步的,且不特殊設置的情況下,Queue只會在一個節(jié)點存在??赡苡械耐瑢W會提另一個問題,我從三個MQ幾點的監(jiān)控面板,都可以看到這個Queue?這個是對的,這是由于Queue的元數(shù)據(jù)也是在三個節(jié)點之間同步,但Queue的實際存儲只會在一個節(jié)點。我們發(fā)送消息到指定Queue,其實是發(fā)送消息到指定節(jié)點下的Queue。如下圖所示,消息發(fā)送至隊列testQueue,無論發(fā)送者通過哪個MQ節(jié)點執(zhí)行發(fā)送,其最終的執(zhí)行都會是在MQ03節(jié)點執(zhí)行消息的存儲。

說到這兒,可能有的小伙伴就要問了?說好的,RabbitMQ集群提供高可用性呢。
分析一下,RabbitMQ集群搭建完成后,如果不進行任何高可用配置,會有哪些問題呢?
單點故障會導致消息丟失:如果MQ03節(jié)點故障,那么MQ03 中的消息就會丟失
無法最大化的利用MQ提供,提升執(zhí)行效率:既然每次發(fā)送到隊列testQueue的消息都會在MQ03節(jié)點存儲,那么何必搭建集群。
引入RabbitMQ的鏡像隊列機制,將queue鏡像到cluster中其他的節(jié)點之上。在該實現(xiàn)下,如果集群中的一個節(jié)點失效了,queue能自動地切換到鏡像中的另一個節(jié)點以保證服務的可用性。在通常的用法中,針對每一個鏡像隊列都包含一個master和多個slave,分別對應于不同的節(jié)點。slave會準確地按照master執(zhí)行命令的順序進行命令執(zhí)行,故slave與master上維護的狀態(tài)應該是相同的。除了publish外所有動作都只會向master發(fā)送,然后由master將命令執(zhí)行的結果廣播給slave們,故看似從鏡像隊列中的消費操作實際上是在master上執(zhí)行的。
一旦完成了選中的slave被提升為master的動作,發(fā)送到鏡像隊列的message將不會再丟失:publish到鏡像隊列的所有消息總是被直接publish到master和所有的slave之上。這樣一旦master失效了,message仍然可以繼續(xù)發(fā)送到其他slave上。
簡單來說,鏡像隊列機制就是將隊列在三個節(jié)點之間設置主從關系,消息會在三個節(jié)點之間進行自動同步,且如果其中一個節(jié)點不可用,并不會導致消息丟失或服務不可用的情況,提升MQ集群的整體高可用性。
先來看下設置鏡像隊列后的效果: 鏡像隊列會出現(xiàn)+2標識。

1.設置隊列為鏡像隊列:How
?兩種方式:
通過監(jiān)控面板設置

通過命令設置
rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]
-p Vhost: 可選參數(shù),針對指定vhost下的queue進行設置
Name: policy的名稱
Pattern: queue的匹配模式(正則表達式)
Definition:鏡像定義,包括三個部分ha-mode, ha-params, ha-sync-mode
? ? ? ? ha-mode:指明鏡像隊列的模式,有效值為 all/exactly/nodes
? ? ? ? all:表示在集群中所有的節(jié)點上進行鏡像
? ? ? ? exactly:表示在指定個數(shù)的節(jié)點上進行鏡像,節(jié)點的個數(shù)由ha-params指定
? ? ? ? nodes:表示在指定的節(jié)點上進行鏡像,節(jié)點名稱通過ha-params指定
? ? ? ? ha-params:ha-mode模式需要用到的參數(shù)
? ? ? ? ha-sync-mode:進行隊列中消息的同步方式,有效值為automatic和manual
priority:可選參數(shù),policy的優(yōu)先級
?請注意一個事實,鏡像配置的pattern 采用的是正則表達式匹配,也就是說會匹配一組。
RabbitMQ集群節(jié)點失效,MQ處理策略:
如果某個slave失效了,系統(tǒng)處理做些記錄外幾乎啥都不做:master依舊是master,客戶端不需要采取任何行動,或者被通知slave失效。
如果master失效了,那么slave中的一個必須被選中為master。被選中作為新的master的slave通常是最老的那個,因為最老的slave與前任master之間的同步狀態(tài)應該是最好的。然而,特殊情況下,如果存在沒有任何一個slave與master完全同步的情況,那么前任master中未被同步的消息將會丟失。
鏡像隊列消息的同步:
? ? ?將新節(jié)點加入已存在的鏡像隊列時,默認情況下ha-sync-mode=manual,鏡像隊列中的消息不會主動同步到新節(jié)點,除非顯式調用同步命令。當調用同步命令后,隊列開始阻塞,無法對其進行操作,直到同步完畢。當ha-sync-mode=automatic時,新加入節(jié)點時會默認同步已知的鏡像隊列。由于同步過程的限制,所以不建議在生產(chǎn)的active隊列(有生產(chǎn)消費消息)中操作。
rabbitmqctl list_queues name slave_pids synchronised_slave_pids? 查看那些slaves已經(jīng)完成同步
rabbitmqctl sync_queue name? ? 手動的方式同步一個queue
rabbitmqctl cancel_sync_queue name 取消某個queue的同步功能
以上針對消息同步的命令,均可以通過監(jiān)控界面來進行操作,最終也是通過這些操作命令執(zhí)行。
說明:
鏡像隊列不是負載均衡,鏡像隊列無法提升消息的傳輸效率,或者更進一步說,由于鏡像隊列會在不同節(jié)點之間進行同步,會消耗消息的傳輸效率。
對exclusive隊列設置鏡像并不會有任何作用,因為exclusive隊列是連接獨占的,當連接斷開,隊列自動刪除。所以實際上這兩個參數(shù)對exclusive隊列沒有意義。那么有哪些隊列是exclusive呢?一般來說,發(fā)布訂閱隊列及設置了該參數(shù)的隊列都是exclusive 排他性隊列。 如何確定一個隊列是不是排他性隊列呢? 如果隊列的features包含Excl,就代表它是排他性隊列。

?鏡像隊列中某個節(jié)點宕掉的后果:
? 當slave宕掉了,除了與slave相連的客戶端連接全部斷開之外,沒有其他影響。
? ?當master宕掉時,會有以下連鎖反應:
1. 與master相連的客戶端連接全部斷開;
2.選舉最老的slave節(jié)點為master。若此時所有slave處于未同步狀態(tài),則未同步部分消息丟失;
3.新的master節(jié)點requeue所有unack消息,在此我向大家推薦一個架構學習交流圈:830478757 ?幫助突破瓶頸 提升思維能力,因為這個新節(jié)點無法區(qū)分這些unack消息是否已經(jīng)到達客戶端,亦或是ack消息丟失在老的master的鏈路上,亦或者是丟在master組播ack消息到所有slave的鏈路上。所以處于消息可靠性的考慮,requeue所有unack的消息。此時客戶端可能有重復消息;
4.如果客戶端連著slave,并且Basic.Consume消費時指定了x-cancel-on-ha-failover參數(shù),那么客戶端會受到一個Consumer Cancellation Notification通知。如果未指定x-cancal-on-ha-failover參數(shù),那么消費者就無法感知master宕機,會一直等待下去。
這就告訴我們,集群中存在鏡像隊列時,重新master節(jié)點有風險。
鏡像隊列中節(jié)點啟動順序,非常有講究:?
假設集群中包含兩個節(jié)點,一般生產(chǎn)環(huán)境會部署三個節(jié)點,但為了方便說明,采用兩個節(jié)點的形式進行說明。
場景1:A先停,B后停
該場景下B是master,只要先啟動B,再啟動A即可?;蛘呦葐覣,再在30s之內啟動B即可恢復鏡像隊列。(如果沒有在30s內回復B,那么A自己就停掉自己)
場景2:A,B同時停
該場景下可能是由掉電等原因造成,只需在30s內聯(lián)系啟動A和B即可恢復鏡像隊列。
場景3:A先停,B后停,且A無法恢復。
因為B是master,所以等B起來后,在B節(jié)點上調用rabbitmqctl forget_cluster_node A以接觸A的cluster關系,再將新的slave節(jié)點加入B即可重新恢復鏡像隊列。
場景4:A先停,B后停,且B無法恢復
該場景比較難處理,舊版本的RabbitMQ沒有有效的解決辦法,在現(xiàn)在的版本中,因為B是master,所以直接啟動A是不行的,當A無法啟動時,也就沒版本在A節(jié)點上調用rabbitmqctl forget_cluster_node B了,新版本中forget_cluster_node支持-offline參數(shù),offline參數(shù)允許rabbitmqctl在離線節(jié)點上執(zhí)行forget_cluster_node命令,迫使RabbitMQ在未啟動的slave節(jié)點中選擇一個作為master。當在A節(jié)點執(zhí)行rabbitmqctl forget_cluster_node -offline B時,RabbitMQ會mock一個節(jié)點代表A,執(zhí)行forget_cluster_node命令將B提出cluster,然后A就能正常啟動了。最后將新的slave節(jié)點加入A即可重新恢復鏡像隊列
場景5:A先停,B后停,且A和B均無法恢復,但是能得到A或B的磁盤文件
這個場景更加難以處理。將A或B的數(shù)據(jù)庫文件($RabbitMQ_HOME/var/lib目錄中)copy至新節(jié)點C的目錄下,再將C的hostname改成A或者B的hostname。如果copy過來的是A節(jié)點磁盤文件,按場景4處理,如果拷貝過來的是B節(jié)點的磁盤文件,按場景3處理。最后將新的slave節(jié)點加入C即可重新恢復鏡像隊列。
場景6:A先停,B后停,且A和B均無法恢復,且無法得到A和B的磁盤文件
無解。
啟動順序中有一個30s 的概念,這個是MQ 的時間間隔,用于檢測master、slave是否可用,因此30s 非常關鍵。
?對于生產(chǎn)環(huán)境MQ集群的重啟操作,需要分析具體的操作順序,不可無序的重啟,會有可能帶來無法彌補的傷害(數(shù)據(jù)丟失、節(jié)點無法啟動)。
簡單總結下:鏡像隊列是用于節(jié)點之間同步消息的機制,避免某個節(jié)點宕機而導致的服務不可用或消息丟失,且針對排他性隊列設置是無效的。另外很重要的一點,鏡像隊列機制不是負載均衡。