網(wǎng)絡(luò)分區(qū)的意義
RabbitMQ采用的鏡像隊(duì)列是一種環(huán)形的邏輯結(jié)構(gòu),如下圖:

RabbitMQ 除了發(fā)布(Publish)消息之外,所有的其余操作都是在master上完成,之后再將有影響的操作同步到slave節(jié)點(diǎn)上。如果客戶端連接的是slave節(jié)點(diǎn),RabbitMQ機(jī)制也會(huì)先將連接路由到master節(jié)點(diǎn)上。比如確認(rèn)(Ack)一條消息,先在A節(jié)點(diǎn)上,即master節(jié)點(diǎn)上確認(rèn),之后再轉(zhuǎn)向B節(jié)點(diǎn),進(jìn)而是C和D節(jié)點(diǎn),最后再D返回Ack之后才真正將這條消息確認(rèn),進(jìn)而標(biāo)記為可刪除。這個(gè)種復(fù)制的原理可以保證更強(qiáng)的數(shù)據(jù)一致性,在這種一致性模型下,如果出現(xiàn)網(wǎng)絡(luò)波動(dòng)或者網(wǎng)絡(luò)延遲等,那么整個(gè)復(fù)制鏈的性能就會(huì)下降。就以上圖為例,如果C節(jié)點(diǎn)網(wǎng)絡(luò)異常,那么整個(gè)A->B->C->D->A的循環(huán)復(fù)制過(guò)程就會(huì)大受影響,整個(gè)RabbitMQ服務(wù)性能將大打折扣,所以這里就需要引入網(wǎng)絡(luò)分區(qū)來(lái)將異常的節(jié)點(diǎn)排離出整個(gè)分區(qū)之外,以確保整個(gè)RabbitMQ的性能。待網(wǎng)絡(luò)情況轉(zhuǎn)好之后再將此節(jié)點(diǎn)加入集群之中。
網(wǎng)絡(luò)分區(qū)的判定
RabbitMQ中與網(wǎng)絡(luò)分區(qū)的判定相關(guān)的是net_ticktime這個(gè)參數(shù),默認(rèn)為60s。在RabbitMQ集群中的每個(gè)broker節(jié)點(diǎn)會(huì)每隔 net_ticktime/4 (默認(rèn)15s)計(jì)一次tick(如果有任何數(shù)據(jù)被寫(xiě)入節(jié)點(diǎn)中,此節(jié)點(diǎn)被認(rèn)為被ticked),如果在連續(xù)四次某節(jié)點(diǎn)都沒(méi)有被ticked到,則判定此節(jié)點(diǎn)處于down的狀態(tài),其余節(jié)點(diǎn)可以將此節(jié)點(diǎn)剝離出當(dāng)前分區(qū)。將連續(xù)四次的tick時(shí)間即為T,那么T的取值范圍為 0.75ticktime < T < 1.25ticktime。下圖可以形象的描述出這個(gè)取值范圍的原因。

默認(rèn)情況下,在45s<T<75s之間會(huì)判定出網(wǎng)絡(luò)分區(qū)。
RabbitMQ會(huì)將queues,exchanges,bindings等信息存儲(chǔ)在Erlang的分布式數(shù)據(jù)庫(kù)——Mnesia中,許多圍繞網(wǎng)絡(luò)分區(qū)的一些細(xì)節(jié)都和這個(gè)Mnesia的行為有關(guān)。如果一個(gè)節(jié)點(diǎn)不能在T時(shí)間內(nèi)連上另一個(gè)節(jié)點(diǎn)(這里的連上特指broker節(jié)點(diǎn)之間的內(nèi)部通信),那么Mnesia通常認(rèn)為這個(gè)節(jié)點(diǎn)已經(jīng)down了,就算之后兩個(gè)節(jié)點(diǎn)又重新恢復(fù)內(nèi)部通信,但是這兩個(gè)節(jié)點(diǎn)都會(huì)認(rèn)為對(duì)方已經(jīng)down,Mnesia此時(shí)認(rèn)定發(fā)生了網(wǎng)絡(luò)分區(qū)的情況。這些會(huì)被記錄到RabbitMQ的服務(wù)日志(默認(rèn)在$RABBITMQ_HOME/var/log/rabbitmq/目錄下)之中,如下所示:
=ERROR REPORT==== 16-Jul-2021::15:20:55 ===
Mnesia('rabbit@node1'): ** ERROR ** mnesia_event got {inconsistent_database, running_partitioned_network, 'rabbit@node2'}
當(dāng)一個(gè)節(jié)點(diǎn)起來(lái)的時(shí)候,RabbitMQ會(huì)記錄是否發(fā)生了網(wǎng)絡(luò)分區(qū),你可以通過(guò)WebUI進(jìn)行查看,后臺(tái)會(huì)直接紅字提醒"network partitions";或者可以通過(guò)rabbitmqctl cluster_status命令查看,如果查看到信息中的partitions那一項(xiàng)是空的,就像這樣:
[{nodes,[{disc,['rabbit@node1', 'rabbit@node2']}]},
{running_nodes,['rabbit@node2','rabbit@node1']},
{cluster_name,<<"rabbit@node1">>},
{partitions,[]}]
然而當(dāng)網(wǎng)絡(luò)分區(qū)時(shí),會(huì)變成這樣:
[{nodes, [{disc, ['rabbit@node1','rabbit@node2']}]},
{running_nodes,['rabbit@node1']},
{cluster_name,<<"rabbit@node1">>},
{partitions, [{'rabbit@node1',['rabbit@node2']}]}]
當(dāng)一個(gè)RabbitMQ集群發(fā)生網(wǎng)絡(luò)分區(qū)時(shí),這個(gè)集群會(huì)分成兩個(gè)或者多個(gè)分區(qū),它們各自為政,互相都認(rèn)為對(duì)方分區(qū)的節(jié)點(diǎn)已經(jīng)down,包括queues,bindings,exchanges這些信息的創(chuàng)建和銷毀都處于自身分區(qū)內(nèi),與其它分區(qū)無(wú)關(guān)。如果原集群中配置了鏡像隊(duì)列,而這個(gè)鏡像隊(duì)列又牽涉到兩個(gè)或者多個(gè)網(wǎng)絡(luò)分區(qū)中的節(jié)點(diǎn)時(shí),每一個(gè)網(wǎng)絡(luò)分區(qū)中都會(huì)出現(xiàn)一個(gè)master節(jié)點(diǎn),如果分區(qū)節(jié)點(diǎn)個(gè)數(shù)充足,也會(huì)出現(xiàn)新的slave節(jié)點(diǎn),對(duì)于各個(gè)網(wǎng)絡(luò)分區(qū),彼此的隊(duì)列都是相互獨(dú)立的,當(dāng)然也會(huì)有一些其他未知的、怪異的事情發(fā)生。當(dāng)網(wǎng)絡(luò)恢復(fù)時(shí),網(wǎng)絡(luò)分區(qū)的狀態(tài)還是會(huì)保持,除非采取一些措施去解決他。
手動(dòng)處理網(wǎng)絡(luò)分區(qū)
為了從網(wǎng)絡(luò)分區(qū)中恢復(fù),首先需要挑選一個(gè)信任的分區(qū),這個(gè)分區(qū)才有決定Mnesia內(nèi)容的權(quán)限,發(fā)生在其他分區(qū)的改變將不被記錄到Mnesia中而直接丟棄。手動(dòng)恢復(fù)網(wǎng)絡(luò)分區(qū)有兩種思路:
- 停止其他分區(qū)中的節(jié)點(diǎn),然后重新啟動(dòng)這些節(jié)點(diǎn)。最后重啟信任分區(qū)中的節(jié)點(diǎn),以去除告警。
- 關(guān)閉整個(gè)集群的節(jié)點(diǎn),然后再啟動(dòng)每一個(gè)節(jié)點(diǎn),這里需確保你啟動(dòng)的第一個(gè)節(jié)點(diǎn)在你所信任的分區(qū)之中。
停止/啟動(dòng)節(jié)點(diǎn)有兩種操作方式:
- rabbimqctl stop/ rabbitmq-server -detached
- rabbitmqctl stop_app/ rabbitmqctl start_app
自動(dòng)處理網(wǎng)絡(luò)分區(qū)
RabbitMQ提供了4種處理網(wǎng)絡(luò)分區(qū)的方式,在rabbitmq.config中配置cluster_partition_handling參數(shù)即可,分別為:
- ignore
- pause_minority
- pause_if_all_down, [nodes], ignore|autoheal
- autoheal
1. ignore
默認(rèn)是ignore,如果不配置rabbitmq.config或者按如下配置:
[
{
rabbit, [
{cluster_partition_handling, ignore}
]
}
].
ignore的配置是當(dāng)網(wǎng)絡(luò)分區(qū)的時(shí)候,RabbitMQ不會(huì)自動(dòng)做任何處理,即需要手動(dòng)處理。
2. pause_minority
在rabbitmq.config配置文件中配置:
[
{
rabbit, [
{cluster_partition_handling, pause_minority}
]
}
].
當(dāng)發(fā)生網(wǎng)絡(luò)分區(qū)時(shí),集群中的節(jié)點(diǎn)在觀察到某些節(jié)點(diǎn)down掉時(shí),會(huì)自動(dòng)檢測(cè)其自身是否處于少數(shù)派(小于或者等于集群中一般的節(jié)點(diǎn)數(shù))。少數(shù)派中的節(jié)點(diǎn)在分區(qū)發(fā)生時(shí)會(huì)自動(dòng)關(guān)閉,當(dāng)分區(qū)結(jié)束時(shí)又會(huì)啟動(dòng)。這里的關(guān)閉是指RabbitMQ application關(guān)閉,而Erlang VM并不關(guān)閉,這個(gè)類似于執(zhí)行了rabbitmqctl stop_app命令。處于關(guān)閉的節(jié)點(diǎn)會(huì)每秒檢測(cè)一次是否可連通到剩余集群中,如果可以則啟動(dòng)自身的應(yīng)用,相當(dāng)于執(zhí)行rabbitmqctl start_app命令。這種處理方式適合集群節(jié)點(diǎn)數(shù)大于2個(gè)且最好為奇數(shù)的情況。
3. pause_if_all_down
在pause_if_all_down模式下,RabbitMQ會(huì)自動(dòng)關(guān)閉不能和list中節(jié)點(diǎn)通信的節(jié)點(diǎn)。語(yǔ)法為{pause_if_all_down, [nodes], ignore|autoheal},其中[nodes]即為前面所說(shuō)的list。如果一個(gè)節(jié)點(diǎn)與list中的所有節(jié)點(diǎn)都無(wú)法通信時(shí),自關(guān)閉其自身。如果list中的所有節(jié)點(diǎn)都down時(shí),其余節(jié)點(diǎn)如果是ok的話,也會(huì)根據(jù)這個(gè)規(guī)則去關(guān)閉其自身,此時(shí)集群中所有的節(jié)點(diǎn)會(huì)關(guān)閉。如果某節(jié)點(diǎn)能夠與list中的節(jié)點(diǎn)恢復(fù)通信,那么會(huì)啟動(dòng)其自身的RabbitMQ應(yīng)用,慢慢的集群可以恢復(fù)。
有兩種配置如下:
[
{
rabbit, [
{cluster_partition_handling, {pause_if_all_down, ['rabbit@node1'], ignore}}
]
}
].
和
[
{
rabbit, [
{cluster_partition_handling, {pause_if_all_down, ['rabbit@node1'], autoheal}}
]
}
].
為什么這里會(huì)有ignore和autoheal兩種不同的配置,考慮這樣一種情況:有兩個(gè)節(jié)點(diǎn)node1和node2在機(jī)架A上,node3和node4在機(jī)架B上,此時(shí)機(jī)架A和機(jī)架B的通信出現(xiàn)異常,如果此時(shí)使用pause-minority的話會(huì)關(guān)閉所有的節(jié)點(diǎn),如果此時(shí)采用pause-if-all-down,list中配置成[‘node1’, ‘node3’]的話,集群中的4個(gè)節(jié)點(diǎn)都不會(huì)關(guān)閉,但是會(huì)形成兩個(gè)分區(qū),此時(shí)就需要ignore或者autoheal來(lái)指引如何處理此種分區(qū)的情形。
4. autoheal
在autoheal模式下,當(dāng)認(rèn)為發(fā)生網(wǎng)絡(luò)分區(qū)時(shí),RabbitMQ會(huì)自動(dòng)決定一個(gè)獲勝的(winning)分區(qū),然后重啟不在這個(gè)分區(qū)中的節(jié)點(diǎn)以恢復(fù)網(wǎng)絡(luò)分區(qū)。一個(gè)獲勝的分區(qū)是指客戶端連接最多的一個(gè)分區(qū)。如果產(chǎn)生一個(gè)平局,既有兩個(gè)或者多個(gè)分區(qū)的客戶端連接數(shù)一樣多,那么節(jié)點(diǎn)數(shù)最多的一個(gè)分區(qū)就是獲勝的分區(qū)。如果此時(shí)節(jié)點(diǎn)數(shù)也一樣多,將會(huì)以一種特殊的方式來(lái)挑選獲勝分區(qū)。
配置示例如下:
[
{
rabbit, [
{cluster_partition_handling, autoheal}
]
}
].