1. 程式人生 > 實用技巧 >RabbitMQ 分割槽腦裂處理策略

RabbitMQ 分割槽腦裂處理策略

原文連結:https://blog.csdn.net/u013256816/article/details/73757884

網路分割槽的意義

RabbitMQ的模型類似交換機模型,且採用erlang這種電信網路方面的專用語言實現。RabbitMQ叢集是不能跨LAN部署(如果要WAN部署需要採用專門的外掛)的,也就是基於網路情況良好的前提下執行的。這種假設就好比paxos並不解決拜占庭問題。

為什麼RabbitMQ需要這種前提假設?這個它本身的資料一致性複製原理有關。RabbitMQ採用的映象佇列是一種環形的邏輯結構,如下圖:

RabbitMQ除了釋出(Publish)訊息之外,所有的其餘操作都是在master上完成,之後再將有影響的操作同步到slave節點上。如果客戶端連線的是slave節點,RabbitMQ機制也會先將連結路由到master節點上。比如確認(Ack)一條訊息,先在A節點上,即master節點上確認,之後再轉向B節點,進而是C和D節點,最後再D返回Ack之後才真正將這條訊息確認,進而標記為可刪除。這個種複製原理和zookeeper的quorum原理不同,它可以保證更強的資料一致性。在這種一致性模型下,如果出現網路波動或者網路延遲等,那麼整個複製鏈的效能就會下降。就以上圖為例,如果C節點網路異常,那麼整個A->B->C->D->A的迴圈複製過程就會大受影響,整個RabbitMQ服務效能將大打折扣,所以這裡就需要引入網路分割槽來將異常的節點排離出整個分割槽之外,以確保整個RabbitMQ的效能。待網路情況轉好之後再將此節點加入叢集之中。

網路分割槽的判定

RabbitMQ中與網路分割槽的判定相關的是net_ticktime這個引數,預設為60s。在RabbitMQ叢集中的每個broker節點會每隔 net_ticktime/4 (預設15s)計一次tick(如果有任何資料被寫入節點中,此節點被認為被ticked),如果在連續四次某節點都沒有被ticked到,則判定此節點處於down的狀態,其餘節點可以將此節點剝離出當前分割槽。將連續四次的tick時間即為T,那麼T的取值範圍為 0.75ticktime < T < 1.25ticktime。下圖可以形象的描述出這個取值範圍的原因。(每個節點代表一次tick判定的timestamp,在兩個臨界值的情況下會有4個tick的判定)

預設情況下,在45s<T<75s之間會判定出網路分割槽。

RabbitMQ會將queues,exchanges,bindings等資訊儲存在Erlang的分散式資料庫——Mnesia中,許多圍繞網路分割槽的一些細節都和這個Mnesia的行為有關。如果一個節點不能在T時間內連上另一個節點(這裡的連上特指broker節點之間的內部通訊),那麼Mnesia通常認為這個節點已經down了,就算之後兩個節點又重新恢復內部通訊,但是這兩個節點都會認為對方已經down,Mnesia此時認定發生了網路分割槽的情況。這些會被記錄到RabbitMQ的服務日誌(預設在$RABBITMQ_HOME/var/log/rabbitmq/目錄下)之中,如下所示:

=ERROR REPORT==== 16-Jul-2017::15:20:55 ===
Mnesia('rabbit@node1'): ** ERROR ** mnesia_event got {inconsistent_database, running_partitioned_network, 'rabbit@node2'}
  • 1
  • 2

當一個節點起來的時候,RabbitMQ會記錄是否發生了網路分割槽,你可以通過WebUI進行檢視;或者可以通過rabbitmqctl cluster_status命令檢視,如果檢視到資訊中的partitions那一項是空的,就想這樣:

[{nodes,[{disc,['rabbit@node1', 'rabbit@node2']}]},
 {running_nodes,['rabbit@node2','rabbit@node1']},
 {cluster_name,<<"rabbit@node1">>},
 {partitions,[]}]
  • 1
  • 2
  • 3
  • 4

然而當網路分割槽時,會變成這樣:

[{nodes,  [{disc,  ['rabbit@node1','rabbit@node2']}]},
 {running_nodes,['rabbit@node1']},
 {cluster_name,<<"rabbit@node1">>},
 {partitions,  [{'rabbit@node1',['rabbit@node2']}]}]
  • 1
  • 2
  • 3
  • 4

當一個RabbitMQ叢集發生網路分割槽時,這個叢集會分成兩個或者多個分割槽,它們各自為政,互相都認為對方分割槽的節點已經down,包括queues,bindings,exchanges這些資訊的建立和銷燬都處於自身分割槽內,與其它分割槽無關。如果原叢集中配置了映象佇列,而這個映象佇列又牽涉到兩個或者多個網路分割槽中的節點時,每一個網路分割槽中都會出現一個master節點,如果分割槽節點個數充足,也會出現新的slave節點,對於各個網路分割槽,彼此的佇列都是相互獨立的,當然也會有一些其他未知的、怪異的事情發生。當網路恢復時,網路分割槽的狀態還是會保持,除非採取一些措施去解決他。

手動處理網路分割槽

為了從網路分割槽中恢復,首先需要挑選一個信任的分割槽,這個分割槽才有決定Mnesia內容的許可權,發生在其他分割槽的改變將不被記錄到Mnesia中而直接丟棄。手動恢復網路分割槽有兩種思路:

  1. 停止其他分割槽中的節點,然後重新啟動這些節點。最後重啟信任分割槽中的節點,以去除告警。
  2. 關閉整個叢集的節點,然後再啟動每一個節點,這裡需確保你啟動的第一個節點在你所信任的分割槽之中。
    停止/啟動節點有兩種操作方式:
  3. rabbimqctl stop/ rabbitmq-server -detached
  4. rabbitmqctl stop_app/ rabbitmqctl start_app

自動處理網路分割槽

RabbitMQ提供了4種處理網路分割槽的方式,在rabbitmq.config中配置cluster_partition_handling引數即可,分別為:

  1. ignore
  2. pause_minority
  3. pause_if_all_down, [nodes], ignore|autoheal
  4. autoheal

1.ignore

預設是ignore,如果不配置rabbitmq.config或者按如下配置:

 [
        {
                rabbit, [
                        {cluster_partition_handling, ignore}
                ]

        }
 ].
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

ignore的配置是當網路分割槽的時候,RabbitMQ不會自動做任何處理,即需要手動處理。

2.pause_minority

在rabbitmq.config配置檔案中配置:

 [
        {
                rabbit, [
                        {cluster_partition_handling, pause_minority}
                ]
        }
 ].
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

當發生網路分割槽時,叢集中的節點在觀察到某些節點down掉時,會自動檢測其自身是否處於少數派(小於或者等於叢集中一般的節點數)。少數派中的節點在分割槽發生時會自動關閉,當分割槽結束時又會啟動。這裡的關閉是指RabbitMQ application關閉,而Erlang VM並不關閉,這個類似於執行了rabbitmqctl stop_app命令。處於關閉的節點會每秒檢測一次是否可連通到剩餘叢集中,如果可以則啟動自身的應用,相當於執行rabbitmqctl start_app命令。

需要注意的是RabbitMQ也會關閉不是嚴格意義上的大多數。比如在一個叢集中只有兩個節點的時候並不適合採用pause-minority模式,因為由於其中任何一個節點失敗而發生網路分割槽時,兩個節點都會被關閉。當網路恢復時,有可能兩個節點會自動啟動恢復網路分割槽,也有可能還是保持關閉狀態。然而如果叢集中的節點遠大於兩個時,pause_minority模式比ignore模式更加的可靠,特別是網路分割槽通常是由於單個節點網路故障而脫離原有分割槽引起的。不過也需要考慮2v2, 3v3這種情況,可能會引起所有叢集節點的關閉。這種處理方式適合叢集節點數大於2個且最好為奇數的情況。

3.pause_if_all_down

在pause_if_all_down模式下,RabbitMQ會自動關閉不能和list中節點通訊的節點。語法為{pause_if_all_down, [nodes], ignore|autoheal},其中[nodes]即為前面所說的list。如果一個節點與list中的所有節點都無法通訊時,自關閉其自身。如果list中的所有節點都down時,其餘節點如果是ok的話,也會根據這個規則去關閉其自身,此時叢集中所有的節點會關閉。如果某節點能夠與list中的節點恢復通訊,那麼會啟動其自身的RabbitMQ應用,慢慢的叢集可以恢復。

有兩種配置如下:

 [
        {
                rabbit, [
                        {cluster_partition_handling, {pause_if_all_down,  ['rabbit@node1'], ignore}}
                ]
        }
 ].
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

和:

 [
        {
                rabbit, [
                        {cluster_partition_handling, {pause_if_all_down,  ['rabbit@node1'], autoheal}}
                ]
        }
 ].
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

為什麼這裡會有ignore和autoheal兩種不同的配置,考慮這樣一種情況:有兩個節點node1和node2在機架A上,node3和node4在機架B上,此時機架A和機架B的通訊出現異常,如果此時使用pause-minority的話會關閉所有的節點,如果此時採用pause-if-all-down,list中配置成[‘node1’, ‘node3’]的話,叢集中的4個節點都不會關閉,但是會形成兩個分割槽,此時就需要ignore或者autoheal來指引如何處理此種分割槽的情形。

4.autoheal

在autoheal模式下,當認為發生網路分割槽時,RabbitMQ會自動決定一個獲勝的(winning)分割槽,然後重啟不在這個分割槽中的節點以恢復網路分割槽。一個獲勝的分割槽是指客戶端連線最多的一個分割槽。如果產生一個平局,既有兩個或者多個分割槽的客戶端連線數一樣多,那麼節點數最多的一個分割槽就是獲勝的分割槽。如果此時節點數也一樣多,將會以一種特殊的方式來挑選獲勝分割槽。

配置示例如下:

 [
        {
                rabbit, [
                        {cluster_partition_handling, autoheal}
                ]
        }
 ].
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

上面所說的特殊的方式其實是和引數的輸入有關,檢視相關程式碼((autoheal的原始碼地址:https://github.com/rabbitmq/rabbitmq-server/blob/master/src/rabbit_autoheal.erl)):

首先是計算連線數(Connections),從程式碼看lists:sort是從小到大排列,然後做一個reverse,這樣就從大到小排列。二級排序是根據分割槽中的節點個數排序,即{_,P}<-Sorted。之後如果連線數和分割槽中節點的個數都相等,那麼應該就看引數輸入的順序了,順序在前則為winning partition。