若是RabbitMQ集羣中只有一個Broker節點,那麼該節點的失效將致使總體服務的臨時性不可用,而且也可能會致使消息的丟失。能夠將全部的消息都設置爲持久化,而且對應的隊列也能夠將durable屬性設置爲true,可是這樣仍然沒法避免因爲緩存的問題:由於在消息發送後和被寫入磁盤並執行刷盤動做之間存在一個短暫卻會產生問題的時間窗。經過publisher confirm機制可以確保客戶端知道哪些消息已經存入磁盤,儘管如此,通常不但願遇到單點故障致使服務不可用。node
若是RabbitMQ集羣是由多個Broker節點組成的,那麼從服務的總體性可用性上來說,該集羣對於單點故障是由彈性的,可是也要注意:儘管交換器和綁定關係可以在單點故障問題上倖免於難,可是隊列和其上的存儲的消息卻不行,這是由於隊列進程及其內容僅僅維持在單個節點上,因此一個節點的失效表現爲其對應的隊列不可用。緩存
引入鏡像隊列機制,能夠將隊列鏡像到集羣中的其餘Broker節點之上,若是集羣中的一個節點失效了,隊列可以自動的切換到鏡像中的另外一個節點上以保證服務的可用性。在一般的用法中,針對每個配置鏡像的隊列都包含一個主節點(master)和若干個從節點(slave)。app
slave會準確的按照master的執行命令的順序進行動做,故slave與master上維護的狀態應該是相同的。若是master因爲某種緣由失效,那麼「資歷最老」的slave會被提高爲新的master。根據slave加入的時間排序,時間最長的slave即爲「資歷最老」。發送到鏡像隊列的全部消息會被同時發往master和全部的slave上,若是此時master掛掉了,消息還會在slave上,這樣slave提高爲master的時候消息也不會丟失。除發送消息(Basic.publish)外的全部動做都只會向master發送,而後再由master將命令執行的結果廣播給各個slave。負載均衡
若是消費者與slave創建鏈接並進行訂閱消費,其實質上都是從master上獲取消息,只不過看似是從slave上消費而已。好比消費者與slave創建了TCP鏈接以後執行一個Basic.Get操做,那麼首先是由slave將Basic.Get請求發往master,再由master準備好數據返回給slave,最後由slave投遞給消費者。這裏可能有疑問?大多數的讀寫壓力都落到了master上,那麼這樣是否負載作不到有效的均衡?或者說是否能夠像MySQL同樣可以實現master寫而slave讀呢?注意這裏的master和slave是針對隊列而言的,而隊列能夠均勻的散落在集羣的各個Broker節點以達到負載均衡的目的,由於真正的負載仍是針對實際的物理機器而言的,而不是內存中駐留的隊列進程。spa
注意要點:操作系統
RabbitMQ的鏡像隊列同時支持publisher confirm和事務兩種機制。在事務機制中,只有當前事務在所有的鏡像中執行以後,客戶端纔會收到Tx.Commit-Ok的消息。一樣的,在publisher confirm機制中,生產者進行當前消息確認的前提是該消息被所有進行所接收了。code
不一樣於普通的非鏡像隊列,鏡像隊列的backing_queue比較特殊,其實現並不是是rabbit_variable_queue,它內部包裹了普通backing_queue進行本地消息的持久化處理,在此基礎上增長了將消息和ack複製到全部鏡像的功能。鏡像隊列的結構能夠參考下圖:blog
能夠看出:master的backing_queue採用的是rabbit_mirror_queue_master,而slave的backing_queue實現是rabbit_mirror_queue_slave。排序
全部對rabbit_mirror_queue_master的操做都會經過組播GM(Guaranteed Multicast)的方式同步到各個slave中。GM負責消息的廣播,rabbit_mirror_queue_slave負責回調處理,而master上的回調處理是由coordinator負責完成的,master對消息進行處理的同時將消息的處理經過GM廣播給全部的slave,slave的GM收到消息後,經過回調交由rabbit_mirror_queue_slave進行實際的處理。接口
GM模塊實現的是一種可靠的組播通訊協議,該協議可以保證組播消息的原子性,即保證組中活着的節點要麼收到消息要麼都收不到,它的實現大體爲:將全部的節點造成一個循環鏈表,每一個節點都會監控位於本身左右兩邊的節點,當有節點新增時,相鄰的節點保證當前廣播的消息會複製到新的節點上;當有節點失效時,相鄰的節點會接管以保證本次廣播的消息複製到全部節點。在master和slave上的這些GM造成一個組,這個組的信息會記錄在Mnesia中,不一樣的鏡像造成不一樣的組。操做命令從master對應的GM發出後,順着鏈表傳送到全部的節點。因爲全部的節點組成了一個循環鏈表,master對應的GM最終會收到本身發出的操做命令,這個時候master就知道該操做命令都同步到了全部的slave上。
注意:每當一個節點加入或者從新加入到這個鏡像鏈路中時,以前隊列保存的內容會被所有清空。
當slave掛掉之後,除了與slave相連的客戶端所有斷開,沒有其餘影響。當master掛掉以後,會有如下連鎖反應:
(1)與master鏈接的客戶端鏈接所有斷開;
(2)選舉最老的slave做爲新的master,由於最老的slave與舊的master之間的同步狀態應該是最好的。若是此時全部的slave處於未同步狀態,則未同步的消息會丟失;
(3)新的master從新入隊全部unack消息,由於新的slave沒法區分這些unack的消息是否已經到達客戶端,或者是ack信息丟失在老的master鏈路上,再或者是丟失在老的master組播ack消息到全部slave的鏈路上,因此出於消息可靠性的考慮,從新入隊全部的unack消息,不過此時客戶端可能會有重複消息;
(4)若是客戶端連着slave,而且Basic.Consume消費時指定了x-cancel-on-ha-failover參數,那麼斷開之時客戶端會收到一個Consumer Cancellation Notification的通知,消費者客戶端中會調用Consumer接口的handleCancel方法。若是未指定x-cancel-on-ha-failover參數,那麼消費者將沒法感知master宕機;
x-cancel-on-ha-failover參數的使用示例以下:
Channel channel = ...; Consumer consumer = ...; Map<String,Object> args = new HashMap<String,Object>(); args.put("x-cancel-on-ha-failover",true); channel.basicConsume("my-queue",false,args,consumer);
鏡像隊列的配置主要是經過Policy來完成的,主要詳細介紹rabbitmqctl set_policy [-p vhost] [--priority priority] [--apply-to apply-to] {name} {pattern} {definition} 命令中的definition部分,對應鏡像隊列的配置來講,definition中包含3個部分:ha-mode、ha-params和ha-sync-mode。
❤ ha-mode:指明鏡像隊列的模式,有效值爲all、exactly、nodes。默認爲all。all表示在集羣中全部的節點上進行鏡像;exactly表示在指定個數的節點上進行鏡像,節點個數由ha-params指定;nodes表示在指定節點上進行鏡像,節點名稱經過ha-params指定,節點的名稱一般相似於rabbit@hostname,能夠經過rabbitmqctl cluster_status命令查看到。
❤ ha-params:不一樣的ha-modee配置中須要用到的參數;
❤ ha-sync-mode:隊列中消息的同步方式,有效值爲automatic何manual;
ha-mode參數對排他隊列並不生效,由於排他隊列是鏈接獨佔的,當鏈接斷開時隊列會自動刪除,因此實際上這個參數對排他隊列沒有任何意義。
將新節點加入到已存在的鏡像隊列中時,默認狀況下ha-sync-mode取值爲manual,鏡像隊列中的消息不會主動同步到新的slave中,除非顯示的調用同步命令。當調用同步命令後,隊列開始阻塞,沒法對其進行其餘操做,直到同步完成。當ha-sync-mode設置爲automatic時,新加入的slave會默認同步已知的鏡像隊列。因爲同步過程的限制,因此不建議對生成環境中正在使用的隊列進行操做。使用rabbitmqctl list_queues {name} slave_pids synchronised_slave_pids命令能夠查看哪些slaves已經同步完成。經過手動方式同步一個隊列的命令爲rabbitmqctl sync_queue {name} ,一樣取消某個隊列的同步操做:rabbitmqctl cancel_sync_queue {name}。
當全部slave都出現未同步狀態,而且ha-promote-on-shutdown設置爲when-synced(默認)時,若是master由於主動緣由停掉,好比經過rabbitmqctl stop命令或者優雅關閉操做系統,那麼slave不會接管master,也就是此時鏡像隊列不可用;可是若是master由於被動緣由停掉,好比Erlang虛擬機或者操做系統崩潰,那麼slave會接管master。這個配置項隱含的價值取向是保證消息可靠不丟失,同時放棄了可用性。若是ha-promote-on-shutdown設置爲always,那麼不論master以何種緣由中止,slave都會接管master,優先保證可用性,不過消息可能會丟失。
鏡像隊列中最後一箇中止的節點會是master,啓動順序必須是master先啓動。若是slave先啓動,它會有30秒的等待時間,等待master的啓動,而後加入的=到集羣中。若是30秒內master沒有啓動,slave會自動中止。當全部節點因故(斷電)同時離線時,每一個節點都認爲本身不是最後中止的節點,要恢復鏡像隊列,能夠嘗試在30秒內啓動全部節點。
參考:《RabbitMQ實戰指南》 朱忠華 編著;