RabbitMQ 網絡分區
一、網絡分區的意義
當出現網絡分區時,不同分區里的節點會認為不屬于自身所在分區的節點都已經掛(down)了,對于隊列、交換器、綁定的操作僅對當前分區有效。
在 RabbitMQ 的默認配置下,即使網絡恢復了也不會自動處理網絡分區帶來的問題,從 3.1 版本開始會自動探測網絡分區,并且提供了相應的配置來解決這個問題。
當網絡恢復時,網絡分區的狀態還是會保持,除非你采取了一些措施去解決它。網絡分區帶來的影響大多是負面的,極端情況下不僅會造成數據丟失,還會影響服務的可用性。那么為什么 RabbitMQ 還要引入網絡分區的設計理念呢?主要是出于它本身的數據一致性復制原理及其性能的考慮。
RabbitMQ 采用的鏡像隊列是一種環形鏈表的邏輯結構,除發送消息外的所有動作都只會向 master 發送,然后再由 master 將命令執行的結果廣播給各個 slave,數據操作沿著環形鏈路傳播,每個節點都確認完成,最后回到 master 才算操作成功。這種復制原理可以保證更強的一致性,但如果出現網絡波動或者網絡故障等異常情況,那么整個數據鏈的性能就會大大降低。如果某個節點網絡異常,那么整個數據鏈就會被阻塞,繼而相關服務也會被阻塞,所以需要引入網絡分區來將異常的節點剝離出整個分區,以確保 RabbitMQ 服務的可用性及可靠性。等待網絡恢復之后,可以進行相應的處理來將此前的異常節點加入集群中。
二、網絡分區的判定
1. 判定原理
RabbitMQ 集群節點內部通信端口默認為 25672,兩兩節點之間都會有信息交互。如果某節點出現網絡故障,或者是端口不通,則會致使與此節點的交互出現中斷,這里就會有個超時判定機制,繼而判定網絡分區。
對于網絡分區的判定是與 net_ticktime 這個參數息息相關的,此參數默認值為 60 秒。在 RabbitMQ 集群內部的每個節點之間會每隔四分之一的 net_ticktime 計一次應答(tick)。如果有任何數據被寫入節點中,則此節點被認為已經被應答(ticked)了。如果連續 4 次,某節點都沒有被 ticked,則可以判定此節點已處于“down”狀態,其余節點可以將此節點剝離出當前分區。
RabbitMQ 不僅會將隊列、交換器及綁定等信息存儲在 Mnesia 數據庫中,而且許多圍繞網絡分區的一些細節也都和這個 Mnesia 的行為相關。如果一個節點不能在連續 4 次 tick 時間內連上另一個節點,那么 Mnesia 通常認為這個節點已經掛了,就算之后兩個節點又重新恢復了內部通信,但是這兩個節點都會認為對方已經掛了,Mnesia 此時認定了發生網絡分區的情況。
2. 檢測方法
(1)RabbitMQ 服務日志
(2)rabbitmqctl cluster_status:根據 partitions 項是否有內容來判斷
(3)Web管理界面:若發生網絡分區,會提示 Network partition detected
(4)HTTP API /api/nodes:獲取節點信息,根據 partitions 項是否有內容來判斷
三、網絡分區的影響
網絡分區的影響根據消息隊列是否配置鏡像而有所不同。
1. 未配置鏡像
對于未配置鏡像的集群,網絡分區發生之后,隊列也會伴隨著宿主節點而分散在各自的分區之中。對于消息發送方而言,可以成功發送消息,但是會有路由失敗的現象,需要配合 mandatory 等機制保障消息的可靠性。對于消息消費方來說,有可能會有詭異、不可預知的現象發生,比如對于已消費消息的 ack 會失效。如果網絡分區發生之后,客戶端與某分區重新建立通信鏈路,其分區中如果沒有相應的隊列進程,則會有異常報出。如果從網絡分區中恢復之后,數據不會丟失,但是客戶端會重復消費。
2. 已配置鏡像
對于已配置鏡像的集群,網絡分區發生之后,master 與 slave 節點之間的角色可能會發生轉換,且同一個隊列在不同分區會產生多個不同的 master,從網絡分區中恢復之后,可能會發生數據丟失。
若配置 ha-sync-mode=automatic,當有新的 slave 出現時,此 slave 會自動同步 master 中的數據。在同步的過程中,集群的整個服務都不可用,客戶端連接會被阻塞。如果 master 中有大量的消息堆積,必然會造成 slave 的同步時間增長,進一步影響了集群服務的可用性。如果配置 ha-sync-mode=manual,有新的 slave 創建的同時不會去同步 master 上舊的數據,如果此時 master 節點又發生了異常,那么此部分數據將會丟失。同樣 ha-promote-on-shutdown 這個參數的影響也需要考慮進來。
網絡分區的發生可能會引起消息的丟失,當然這點也有辦法解決。首先消息發送端要有能夠處理 Basic.Return 的能力。其次,在監測到網絡分區發生之后,需要迅速地掛起所有的生產者進程。之后連接分區中的每個節點消費分區中所有的隊列數據。在消費完之后再處理網絡分區。最后在從網絡分區中恢復之后再恢復生產者的進程。整個過程可以最大程度上保證網絡分區之后的消息的可靠性。同樣也要注意的是,在整個過程中會伴有大量的消息重復,消費者客戶端需要做好相應的冪等性處理。當然也可以采用集群遷移的方法,將所有舊集群的資源都遷移到新集群來解決這個問題。
四、手動處理網絡分區
為了從網絡分區中恢復,首先需要挑選一個信任分區,這個分區才有決定 Mnesia 內容的權限,發生在其他分區的改變將不會被記錄到 Mnesia 中而被直接丟棄。在挑選完信任分區之后,重啟非信任分區中的節點,如果此時還有網絡分區的告警,緊接著重啟信任分區中的節點。
1. 如何挑選信任分區
挑選信任分區一般可以按照這幾個指標進行:分區中要有 disk 節點;分區中的節點數最多;分區中的隊列數最多;分區中的客戶端連接數最多。優先級從前到后。
2. 如何重啟節點
RabbitMQ 中有兩種重啟方式:
(1)
rabbitmqctl stop
rabbitmq-server-detached
(2)
rabbitmqctl stop_app
rabbitmqctl start_app
第一種方式需要同時重啟 Erlang 虛擬機和 RabbitMQ 應用,而第二種方式只是重啟 RabbitMQ 應用。兩種方式都可以從網絡分區中恢復,但是更加推薦使用第二種方式。
3. 重啟的順序有何考究
必須在以下兩種重啟順序中擇其一進行重啟操作:
(1)停止其他非信任分區中的所有節點,然后再啟動這些節點。如果此時還有網絡分區的告警,則再重啟信任分區中的節點以去除告警。
(2)關閉整個集群中的節點,然后再啟動每一個節點,這里需要確保啟動的第一個節點在信任的分區之中。
在選擇哪種重啟順序之前,還需要考慮兩個問題:
(1)如果選擇第二種重啟順序,會有一個嚴重的問題,即 Mnesia 內容權限的歸屬問題。節點重啟后若 Mnesia 數據向非信任分區靠齊,導致最終集群中的 Mnesia 數據是非信任分區,就會造成無法估量的損失。所以第二種重啟順序有可能會引起二次網絡分區的發生。
(2)如果集群配置了鏡像隊列,隨著節點的重啟,會出現所有的隊列的 master 都“漂移”到了同一個節點上,這樣大部分壓力都集中到了這個 master 節點上,從而不能很好地實現負載均衡。為了防止這個現象的發生,可以在重啟之前先刪除鏡像隊列的配置,這樣能夠在一定程度上阻止隊列的“過分漂移”。
![]()
需要在每個分區上都執行刪除鏡像隊列配置的操作,以確保每個分區中的鏡像都被刪除。
4. 手動處理網絡分區的步驟
(1)掛起生產者和消費者進程。這樣可以減少消息不必要的丟失,如果進程數過多,情形又比較緊急,也可跳過此步驟。
(2)刪除鏡像隊列的配置。
(3)挑選信任分區。
(4)關閉非信任分區中的節點。采用 rabbitmqctl stop_app 命令關閉。
(5)啟動非信任分區中的節點。采用與步驟4對應的 rabbitmqctl start_app 命令啟動。
(6)檢查網絡分區是否恢復,如果已經恢復則轉步驟8;如果還有網絡分區的報警則轉步驟7。
(7)重啟信任分區中的節點。
(8)添加鏡像隊列的配置。
(9)恢復生產者和消費者的進程。
五、自動處理網絡分區
RabbitMQ 提供了三種方法自動地處理網絡分區:pause-minority 模式、pause-if-all-down 模式和 autoheal 模式。默認是 ignore 模式,即不自動處理網絡分區,所以在這種模式下,當網絡分區的時候需要人工介入。在 rabbitmq.config 配置文件中配置 cluster_partition_handling 參數即可實現相應的功能。默認的 ignore 模式的配置如下(注意最后有個點號):

1. pause-minority 模式
在 pause-minority 模式下,當發生網絡分區時,集群中的節點在觀察到某些節點“down”的時候,會自動檢測其自身是否處于“少數派”(分區中的節點小于或者等于集群中一半的節點數),RabbitMQ 會自動關閉“少數派”中所有節點的 RabbitMQ 應用,而 Erlang 虛擬機并不關閉,類似于執行了 rabbitmqctl stop_app 命令。處于關閉的節點會每秒檢測一次是否可連通到剩余集群中,如果可以則啟動自身的應用。相當于執行 rabbitmqctl start_app 命令。
當對等分區出現時,會關閉這些分區內的所有節點。只有等待網絡恢復之后,才會自動啟動所有的節點以求從網絡分區中恢復。

2. pause-if-all-down 模式
在 pause-if-all-down 模式下,會配置一個信任節點列表,RabbitMQ 集群中的節點在和所配置的列表中的任何節點都不能交互時才會關閉(受信節點列表):

如果一個節點與信任節點列表中任何節點都無法通信時,則會關閉自身的 RabbitMQ 應用。如果是信任節點列表中的節點本身發生了故障造成網絡不可用,而其他節點都是正常的情況下,這種規則會讓所有的節點中 RabbitMQ 應用都關閉,待信任節點列表中的節點的網絡恢復之后,各個節點再啟動自身應用以從網絡分區中恢復。
注意到 pause-if-all-down 模式下有 ignore 和 autoheal 兩種不同的配置??紤]一種情形,node1 和 node2 部署在機架 A 上,而 node3 和 node4 部署在機架 B 上。此時配置{cluster_partition_handling,{pause_if_all_down,[′rabbit@node1′,′rabbit@node3′],ignore}},那么當機架 A 和機架 B 的通信出現異常時,由于 node1 和 node2 保持著通信,node3 和 node4 保持著通信,這 4 個節點都不會自行關閉,但是會形成兩個分區,所以這樣不能實現自動處理的功能。所以如果將配置中的 ignore 替換成 autoheal 就可以處理此種情形。
3. autoheal 模式
在 autoheal 模式下,當認為發生網絡分區時,RabbitMQ 會自動決定一個獲勝(winning)的分區,然后重啟不在這個分區中的節點來從網絡分區中恢復。一個獲勝的分區是指客戶端連接最多的分區,如果產生一個平局,即有兩個或者多個分區的客戶端連接數一樣多,那么節點數最多的一個分區就是獲勝分區。如果此時節點數也一樣多,將以節點名稱的字典序來挑選獲勝分區。

對于 pause-minority 模式,關閉節點的狀態是在網絡故障時,也就是判定出 net_tick_timeout 之時,會關閉“少數派”分區中的節點,等待網絡恢復之后,即判定出網絡分區之后,啟動關閉的節點來從網絡分區中恢復。autoheal 模式在判定出 net_tick_timeout 之時不做動作,要等到網絡恢復之時,在判定出網絡分區之后才會有相應的動作,即重啟非獲勝分區中的節點。
在 autoheal 模式下,如果集群中有節點處于非運行狀態,那么當發生網絡分區的時候,將不會有任何自動處理的動作。
4. 挑選哪種模式
允許 RabbitMQ 能夠自動處理網絡分區并不一定會有正面的成效,也有可能會帶來更多的問題。網絡分區會導致 RabbitMQ 集群產生眾多的問題,需要對遇到的問題做出一定的選擇。
如果置 RabbitMQ 于一個不可靠的網絡環境下,需要使用 Federation 或者 Shovel。就算從網絡分區中恢復了之后,也要謹防發生二次網絡分區。
ignore 模式:發生網絡分區時,不做任何動作,需要人工介入。
pause-minority 模式:對于對等分區的處理不夠優雅,可能會關閉所有的節點。一般情況下,可應用于非跨機架、奇數節點數的集群中。
pause-if-all-down 模式:對于受信節點的選擇尤為考究,尤其是在集群中所有節點硬件配置相同的情況下。此種模式可以處理對等分區的情形。
autoheal 模式:可以處于各個情形下的網絡分區。但是如果集群中有節點處于非運行狀態,則此種模式會失效。
參考:
《RabbitMQ實戰指南》

浙公網安備 33010602011771號