1. 程式人生 > >八、RabbitMQ的叢集原理

八、RabbitMQ的叢集原理

叢集架構

寫在前面

RabbitMQ叢集是按照低延遲環境設計的,千萬不要跨越WAN或者網際網路來搭建RabbitMQ叢集。如果一定要在高延遲環境下使用RabbitMQ叢集,可以參考使用Shovel和Federation工具。

RabbitMQ社群中的傳統觀念要求叢集中節點數量的上限在32至64個,因為每向叢集新增一個節點,就添加了同步的複雜性。叢集中的每個節點必須知道其他節點的資訊,這種非線性的複雜度會拖慢訊息投遞和叢集管理。

叢集中的佇列

RabbitMQ叢集設計目的有兩個:

  • 允許消費者和生產者在RabbitMQ節點崩潰的情況下繼續執行;
  • 通過新增更多的節點來線性擴充套件訊息通訊的吞吐量;

當一個RabbitMQ叢集節點崩潰時,該節點上佇列的訊息也會消失。這事因為RabbitMQ預設不會將佇列的內容複製到整個叢集上。如果不進行特別的配置,這些訊息僅存在於佇列所屬的那個節點上。

RabbitMQ會始終記錄以下四種類型的內部元資料:

  • 佇列元資料:佇列名稱和它們的屬性;
  • 交換器元資料:交換器名稱、型別和屬性;
  • 繫結元資料:一張簡單的表格展示瞭如何將訊息路由到佇列;
  • vhost元資料:為vhost內的佇列、交換器和繫結提供名稱空間和安全屬性;

當你引入叢集時,RabbitMQ需要追蹤新的元資料型別 —— 叢集節點位置,以及節點與已記錄的其他型別元資料的關係。叢集也提供了將元資料儲存到磁碟或記憶體中的選項。

不是每一個節點都有所有佇列的完全拷貝。
在叢集中建立佇列的話,叢集只會在單個節點(而不是所有節點)上建立完整的佇列資訊。
結果是,只有佇列的所有者節點,知道有關佇列的所有資訊。
其他非佇列所有者的節點們,只知道佇列的元資料和指向佇列存在的那個節點的指標。
因此,當叢集節點崩潰時,該節點的佇列和關聯的繫結就都消失了。
附加在那些佇列上的消費者丟失了訂閱。
並且任何匹配該佇列繫結資訊的新訊息也丟失了。

如果消費者要消費的佇列的所在節點故障了,而該佇列是持久化的,那麼想要繼續消費,唯一的辦法就是恢復故障節點。當失敗節點恢復後加入叢集,該節點上的佇列訊息不會丟失。

嘗試在持久化佇列節點故障後,重新宣告佇列,會得到一個404 NOT_FOUND錯誤。但如果佇列不是持久化的,那麼重新宣告就會成功。

RabbitMQ設計上不將佇列內容和狀態複製到所有的節點上,主要有兩個原因:

儲存空間 —— 如果一個節點可以儲存1GB的訊息,那麼在多個節點之間複製訊息則會浪費多個G的空間;

效能因素 —— 訊息的釋出需要將訊息複製到每一個節點,對於持久化訊息來說,每次都會觸發磁碟活動,每次新增節點,網路和磁碟負載都會增加;

另外,在向佇列傳送訊息時,只有佇列的所有節點,才會收到磁碟操作的影響。其他非所有節點,只需要將接收到的訊息傳遞給所有者節點即可。

因此,往Rabbit叢集中新增更多的節點,就意味著將擁有更多的節點來傳播訊息,多個節點會帶來效能的提升。

叢集中的交換器

交換器,本質上就是一個名稱和佇列繫結的列表。

當你將訊息釋出到交換器時,實際上是由連線的通道,根據訊息上的路由鍵,去交換器繫結列表中進行比較,然後路由訊息到佇列中。

由於交換器只不過是一張表,因此將這張表在叢集中複製很簡單,當建立一個新的交換器時,RabbitMQ所要做的是將交換器新增到叢集中的所有節點上。

叢集的訊息可靠性與單點模式基本一致。AMQP的Basic.Publish命令不會返回訊息的狀態。這意味著當通道節點崩潰時,通道可能仍然在路由訊息,解決方案是使用事務,或釋出確認模式。

叢集節點型別

RabbitMQ的叢集大致上可分為3類:

  • 磁碟節點;
  • 記憶體節點;
  • 統計節點;

當建立叢集時,至少保證一個磁碟節點和若干個記憶體節點。無論哪種節點,都不會影響訊息持久化。

當有多個磁碟節點時,就能在發生硬體故障時更加遊刃有餘。

若存在多個磁碟節點,在發生故障恢復時可能會出現狀態不一致的情況,這種時候,可關閉叢集並按照順序重新啟動節點。

統計節點,只能和磁碟節點搭配使用。它負責收集叢集中每個節點的全部統計資料和狀態資料。在任何時候,一個叢集只能有一個統計節點。

在擁有兩個磁碟節點的叢集中,如果主節點發生故障,統計節點將指派給備用的磁碟節點。

叢集的節點搭配

如果只有一個磁碟節點,當這個節點發生故障後,叢集還是可以繼續路由訊息,但是不能做以下操作:

  • 建立佇列;
  • 建立交換器;
  • 建立繫結關係;
  • 新增使用者;
  • 更改許可權;
  • 新增或刪除叢集節點;

最好是設定一個以上的磁碟節點,在某個磁碟節點故障時,還有備用的磁碟節點可用。

當記憶體節點新增到叢集或重新加入到叢集時,他們會連線到預先配置的磁碟節點,下載叢集元資料。當新增記憶體節點時,確保告知該記憶體節點所有的磁碟節點。

叢集節點升級

叢集的升級是半自動化的,在沒有準備的情況下解壓新版本的RabbitMQ覆蓋舊版本,會抹去叢集上所有配置和資料。如果要保留這些,需要進行一些操作。

首先,通過Management外掛備份配置;

然後,關閉所有生產者並等待所有消費者消費完佇列中所有的訊息(使用rabbitmqctl觀察佇列狀態);

接著,關閉節點並解壓新版本的RabbitMQ;

隨後,選擇其中一個磁碟節點作為升級節點,當它啟動時,該節點會將持久化的叢集資料升級到新版本,然後在啟動其他的磁碟節點;

最後,啟動記憶體節點,這樣就會讓叢集中執行新版本的RabbitMQ了,而且元資料都會保留;

映象佇列

在RabbitMQ2.6版本後,RabbitMQ團隊給叢集帶來了內建的雙冗餘選項:映象佇列。

想普通佇列那樣,映象佇列的主拷貝僅存在於一個節點(主佇列,Master)上,但與普通佇列不同的是,映象節點在叢集中的其他節點上擁有從佇列(slave)拷貝。一旦佇列主節點不可用,最老的從佇列將被選舉為主佇列。

在定義佇列時,加入引數:

x-ha-policy = all

這意味著佇列被映象到叢集中的所有節點上。如果在該佇列宣告之後,叢集又新增了節點,那麼該節點也會自動託管一份佇列的從拷貝。

映象佇列原理

訊息傳送到佇列時,如果佇列是映象佇列,那麼也要將訊息投遞到映象佇列的從拷貝。

想確保訊息沒有丟失的話,一樣可以使用釋出確認模式,不同的是,RabbitMQ會在所有佇列安全的接收了訊息後在通知你。

這裡需要有從釋出者和消費者兩方面說一下異常情況:

  • 釋出者:如果訊息在路由到從拷貝前,主拷貝發生了故障,並且從拷貝升級成了主拷貝,這種情況下,釋出確認永遠不會到達;
  • 消費者:如果映象佇列失去一個節點,則附加在映象佇列上的任何消費者都不會注意到這一樣。但是如果主拷貝節點發生故障,那麼所有該節點的消費者需要重新附加並監聽新的佇列主拷貝;
注意:如果消費者連線在從拷貝,當主拷貝發生變化時,RabbitMQ會發送給客戶端一個Consumer Cancellation的通知,告知客戶端發生了變化。如果你的客戶端不支援這個通知處理的話,你的程式會空跑,以為佇列中沒有訊息可以消費了。

如果你的客戶端不支援這個消費者取消通知處理,那麼應該避免使用映象佇列!!!<