原文鏈接: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中而直接丟棄。手動恢復網絡分區有兩種思路:
- 停止其他分區中的節點,然后重新啟動這些節點。最后重啟信任分區中的節點,以去除告警。
- 關閉整個集群的節點,然后再啟動每一個節點,這里需確保你啟動的第一個節點在你所信任的分區之中。
停止/啟動節點有兩種操作方式: - rabbimqctl stop/ rabbitmq-server -detached
- rabbitmqctl stop_app/ rabbitmqctl start_app
自動處理網絡分區
RabbitMQ提供了4種處理網絡分區的方式,在rabbitmq.config中配置cluster_partition_handling參數即可,分別為:
- ignore
- pause_minority
- pause_if_all_down, [nodes], ignore|autoheal
- 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。