1. 程式人生 > 其它 >【RabbitMQ】訊息佇列中介軟體學習之RabbitMQ(12)

【RabbitMQ】訊息佇列中介軟體學習之RabbitMQ(12)

技術標籤:RabbitMQRabbitMQ網路分割槽pause-minorityautoheal信任分割槽

網路分割槽

1. 網路分割槽的意義

當一個叢集發生網路分割槽時,叢集會分為兩個部分或者更多,它們各自為政,互相都認為對方分割槽內的節點已經掛了,包括佇列、交換器及繫結等元資料的建立和銷燬都處於自身分割槽內,與其他分割槽無關。

分割槽的引入是為了配合RabbitMQ的資料一致性複製原理。一般情況下,網路分割槽都是由於單個節點的故障引起的,且通常會形成一個大分割槽和一個單節點分割槽,如果配置了映象,那麼可以在不影響服務可用性,不丟失訊息的情況下從網路分割槽的情形下得以恢復。

2. 網路分割槽的判定

RabbitMQ叢集節點內部通訊埠預設為25672,兩兩節點之間會有資訊互動,如果某節點出現網路故障或是埠不同,則會使與此節點的互動出現中斷,經過超時判定後,會判定網路分割槽。

對於網路分割槽的判斷與net_ticktime引數息息相關,其預設值為60秒。注意與heartbeat_time的區別,heatbeat_time是指客戶端與RabbitMQ服務之間通訊的心跳時間,針對5672埠而言。如果發生超時,則會有net_tick_timeout的資訊報出。在RabbitMQ叢集內部每個節點間會每隔四分之一net_ticktime計一次應答(tick),如果任何資料被寫入節點中,則此節點會被認為已經被應答(ticked)了,如果連續4此沒有應答,則會判定此節點已下線(down),其餘節點可以將此節點剝離出當前分割槽。

將連續4次的tick時間即為T,那麼T的取值範圍為 0.75ticktime < T < 1.25ticktime。下圖可以形象的描述出這個取值範圍的原因。(每個節點代表一次tick判定的timestamp, 在兩個臨界值的情況下會有4個tick的判定,預設情況下,在45s<T<75s之間會判定net_tick_timeout。)
取值範圍的緣由
RabbitMQ不僅會將佇列、交換器及繫結等資訊儲存在Mnseia資料庫中,而且許多圍繞網路分割槽的一些細節也會儲存在Mnesia資料庫中。如果一個節點不能在T時間連上另一個節點,那麼Mnesia此時認定發生了網路分割槽,並將這些記錄在RabbitMQ服務日誌中

除了服務日誌外,還有以下3種方法可以判定是否出現了網路分割槽

  • 通過rabbitmqctl工具檢視,即採用rabbitmqctl cluster_status命令可以看到叢集相關的資訊,如果partitions項中有相關的內容,則說明出現了網路分割槽;
  • 通過Web管理頁面的方式進行檢視,網路分割槽出現時會出現告警資訊;
  • 通過HTTP API的方式調取節點資訊來檢查是否發生網路分割槽,/api/nodes介面會返回一個Josn字串,若其中出現partitions相關內容則表示出現了網路分割槽:
curl -i -u admin:admin -H "content-type:application/json" -X GET http://localhost:15672/api/nodes

3. 網路分割槽的模擬

正常情況下,很難觀察到RabbitMQ網路分割槽的發生。因此為了更好地理解網路分割槽,需要採用模式的方式實現分割槽,主要分為以下3類方法:

  • iptables封禁/解封IP地址或者埠號
  • 掛起/恢復作業系統
    1. iptables的方式
    由於RabbitMQ叢集內部節點通訊埠預設為25672,可以封禁這個埠來模擬出net_tick_timeout,然後再開啟此埠讓叢集判斷出網路分割槽的發生。

舉例說明,整個RabbitMQ叢集由3個節點組成,分別為node1, node2, node3。此時我們要模擬node2節點被剝離出當前分割槽的情形,即模擬[node1, node3]和[node2]兩個分割槽。可以在node2上執行如下命令以封禁25672埠。如果在配置中修改過這個埠號,可以將下面的命令改成相應的埠號即可。

iptables -A INPUT -p tcp --dport 25672 -j DROP
iptables -A OUTPUT -p tcp --dport 25672 -j DROP

同時需要監測各個節點的服務日誌,當有如下相似資訊出現時即為已經判定出net_tick_timeout:

=INFO REPORT==== 10-Oct-2017::11:53:03 ===
rabbit on node [email protected] down
 
=INFO REPORT==== 10-Oct-2017::11:53:03 ===
node [email protected] down: net_tick_timeout

當然,如果不想去監測各個節點的服務日誌,也可以等待75秒以確保出現net_tick_timeout。注意此時只判定出net_tick_timeout,要等node2網路恢復之後,即解封25672埠之後才會判定出現網路分割槽。解封命令如下:

iptables -D INPUT 1
iptables -D OUTPUT 1

至此,node2節點與其他節點的內部通訊已經恢復,如果此時檢視叢集的狀態可以發現[node1, node3]和[node2]已形成兩個獨立的分割槽。

還可以使用iptables封禁IP地址的方法模擬網路分割槽。假設整個RabbitMQ叢集的節點名稱與其IP地址對應如下:

node1 192.168.0.2
node2 192.168.0.3
node3 192.168.0.4

如果要模擬出[node1, node3]和[node2]兩個分割槽的情形,可以在node2節點上執行:

iptables -I INPUT -s 192.168.0.2 -j DROP
iptables -I INPUT -s 192.168.0.4-j DROP

對應的解封命令為:

iptables -D INPUT 1
iptables -D INPUT 1

或者也可以分別在node1, node3節點上執行:

iptables -I INPUT -s 192.168.0.3 -j DROP

與其對應的解封命令為:

iptables -D INPUT 1

如果叢集的節點部署有跨網路分段的話,可以採取禁用整個網路段的方式模擬網路分割槽。假設RabbitMQ叢集中3個節點和其對應的IP關係如下:

node1 192.168.0.2
node2 192.168.1.3
node3 192.168.0.4

如果要模擬出[node1, node3]和[node2]兩個分割槽的情形,可以在node2節點上執行:

iptables -I INPUT -s 192.168.0.0/24 -j DROP

對應的解封命令也是:iptables -D INPUT 1

  1. 封禁/解封網絡卡的方式
    操作網絡卡的方式和iptables的方式有相似之處,都是模擬網路故障來產生網路分割槽。首先需要使用ifconfig命令來查詢出當前的網絡卡編號,如下所示,一般情況下單臺機器只有一個網絡卡。(這裡暫時不考慮多網絡卡的情形,因為對於RabbitMQ來說,多網絡卡的情況造成的網路分割槽異常複雜。)
[[email protected] [email protected]]# ifconfig
eth0      Link encap:Ethernet  HWaddr FA:16:3E:A8:FF:31  
          inet addr:192.168.1.2  Bcast:192.168.1.255  Mask:255.255.224.0
          inet6 addr: fe80::f816:3eff:fea8:ff31/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1450  Metric:1
          RX packets:547632449 errors:0 dropped:0 overruns:0 frame:0
          TX packets:5745162 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:82813557343 (77.1 GiB)  TX bytes:3099664440 (2.8 GiB)
 
lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:111152 errors:0 dropped:0 overruns:0 frame:0
          TX packets:111152 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:5401343 (5.1 MiB)  TX bytes:5401343 (5.1 MiB)

假設在node1, node2, node3三個節點的RabbitMQ叢集中,node2的網絡卡編號為eth0,此時要模擬網路分割槽[node1, node3]和[node2]的情形,需要在node2上執行以下命令關閉網絡卡:

ifdown eth0

待判定出net_tick_timeout之後,再開啟網絡卡:

ifup eth0

這樣就可以模擬出網路分割槽的情形。當然也可以使用service network stop和service network start兩個命令來模擬網路分割槽,原理同ifdown/ifup eth0的方式。

  1. 掛起/恢復作業系統的方式
    除了模擬網路故障的方式,作業系統的掛起和恢復操作也會導致叢集內節點的網路分割槽。因為發生掛起的節點不會認為自身已經失敗或者停止工作,但是叢集內的其他節點會這麼認為。如果叢集中的一個節點執行在一臺筆記本上,然後你合上了筆記本,那麼這個節點就掛起了。或者一個更常見的現象,叢集中的一個節點執行在某臺虛擬機器上,然後虛擬機器的管理程式掛起了這個虛擬機器節點,這樣節點就被掛起了。待(0.75net_ticktime, 1.25net_ticktime)時間之後,判斷出net_tick_timeout,再恢復掛起的節點即可以模擬出網路分割槽的情形。

注意:這裡模擬的都是單節點脫離主分割槽的情形,對於分裂成對稱的網路分割槽和多網路分割槽的情形以後再研究。

4. 網路分割槽的影響

4.1 未配置映象

對於未配置映象的叢集,網路分割槽發生之後,佇列也會隨著宿主節點而分散在各自的分割槽中。對於訊息傳送方而言,可以成功傳送訊息,但是會有路由失敗的情況,需要配合mandatory等機制保障訊息的可靠性,對於訊息消費方而言,可能會出現不可預知的現象,比如已消費訊息ack會失效。網路分割槽發生後,客戶端與某分割槽重新建立通訊鏈路,其分割槽中如果沒有相應的佇列程序,則會有異常報出。如果從網路分割槽中恢復,資料不會丟失,但是客戶端會重複消費。

4.2 已配置映象

**網路分割槽的發生會引起訊息的丟失,解決辦法為訊息傳送端需要具備Basic.Return的能力,其次在檢測到網路分割槽之後,需要迅速地掛起所有生產者程序,之後連線分割槽中的每個節點消費分割槽中所有的佇列資料,在消費完之後再處理網路分割槽,最後從網路分割槽中恢復之後再恢復生產者程序。**整個過程可以最大程度上保證網路分割槽之後的訊息的可靠性。需要注意的是整個過程中會有大量的訊息重複,消費客戶端需要做好相應的冪等性處理。也可以將所有舊叢集資源遷移到新叢集來解決這個問題。

5. 手動處理網路分割槽

為了從網路分割槽中恢復,需要挑選一個信任分割槽,該分割槽具有決定Mnesia內容的許可權,發生在其它分割槽的改變將不會被記錄到Mnesia中而被丟棄。挑選完信任分割槽之後,重啟非信任分割槽中的節點,如果還有網路分割槽的告警,緊接著重啟信任分割槽中的節點。這個有三個要點需要注意:

  • 挑選信任分割槽:可以按下面幾個指標進行——分割槽中要有disc節點,分割槽中的節點數最多,分割槽中佇列數量最多,分割槽中客戶端連線數最多。先後順序從前往後(優先順序從前到後)。
  • 重啟方式:RabbitMQ中有兩種重啟方式——使用rabbitmqctl stop命令關閉和rabbitmq-server -detached命令啟動;第二種是使用rabbitmqctl stop_app關閉和rabbitmqctl start_app命令啟動。第一種方式需要同時啟動Erlang虛擬機器和RabbitMQ,第二種只重啟RabbitMQ應用,兩種方式都可以從網路分割槽中恢復,但是更加推薦第二種。
  • 重啟順序:有兩種方式,選擇一種即可,第一種是停止其它非信任分割槽中的所有節點,然後重啟這些節點,如果還有網路分割槽告警則再重啟信任分割槽中的節點以去除告警;第二種是關閉整個叢集中的節點,然後再啟動每一個節點,需要確保啟動的第一個節點在信任分割槽中。在決定重啟順序之前,還需要考慮master節點存在的轉移現象。如果之前配置了映象佇列,那麼從發生網路分割槽到恢復的過程中佇列可能會出現“漂移”的現象。可以在重啟前先刪除映象佇列的配置,這樣可以在一定程度上阻止佇列的”過分漂移“,即阻止可能所有佇列都“漂移”到一個節點上的情況。
    刪除映象佇列的配置可以採用rabbitmqctl工具刪除:
rabbitmqctl cleat_policy [-p vhost] {mirror_queue_name}

也可以通過Web介面或者HTTP API方式進行刪除:

curl -s -u {username:password} -X DELETE http://localhost:15672/api/policies/default/{mirror_queue_name}

與此同時,需要在每個分割槽上都執行刪除映象佇列配置的操作,以確保每個分割槽中的映象都被刪除。

具體的網路分割槽處理步驟如下:

  1. 第一步:掛起生產者和消費者程序。這樣可以減少訊息不必要的丟失,如果程序數過多,情形又比較緊急,也可以跳過此步驟;
  2. 第二步:刪除映象佇列的配置
  3. 第三步:挑選信任分割槽
  4. 第四步:關閉非信任分割槽中的節點,採用rabbitmqctl stop_app命令關閉
  5. 第五步:啟動非信任分割槽中的節點,採用rabbitmqctl start_app命令啟動
  6. 第六步:檢查網路分割槽是否恢復,如果已經恢復,則跳轉到第八步;如果還有網路分割槽的報警,則轉第七步
  7. 第七步:重啟信任分割槽中的節點
  8. 第八步:新增映象佇列的配置
  9. 第九步:恢復生產者和消費者的程序。
  10. 第十步:使用rabbitmqctl cluster_status命令檢測其輸出的partitions項中是否有節點資訊,如果為空,則說明已經恢復。

6. 自動處理網路分割槽

RabbitMQ提供了三種自動處理網路分割槽的方法:pause-minority模式,pause-if-all-down模式和autoheal模式,預設是ignore,即不自動處理網路分割槽,所以在這種模式下,當網路分割槽需要人工介入,則修改rabbitmq.config檔案中的cluster_partition_handling屬性即可實現響應的功能。

6.1 pause-minority模式

在pause-minority模式下,當發生網路分割槽時,叢集中的節點在觀察到某些節點“down”時,會自動檢測其是否屬於“少數派”(分割槽中的節點小於等於叢集中一般的節點數),RabbitMQ會自動關閉這些節點的運作,根據CAP原理,保證了分割槽耐受性,即確保大多數節點可以繼續執行,滿足了可用性。

“少數派”中的節點在分割槽開始時會關閉,當分割槽結束時又會啟動。這裡的關閉是指RabbitMQ應用的關閉,而Erlang虛擬機器並不關閉。處於關閉的節點會每秒檢測一次是否可以連通到剩餘叢集中,如果可以則啟動自身的應用,相當於執行rabbitmqctl start_app命令。

注意,RabbitMQ也會關閉不是嚴格意義上的大多數,如果叢集中只有兩個節點,其中任一節點關閉,另一個節點都會被關閉,所以pause-minority模式適用於叢集節點數大於2個的時候。但如果出現2v2、3v3類的對等節點數的分割槽情況所有節點上的RabbitMQ應用都會被關閉。只有等待網路恢復之後,才會自動啟動所有的節點以求從網路分割槽中恢復。

6.2 pause-if-all-down模式

在pause-if-all-down模式下,RabbitMQ叢集中的節點在和所配置的列表中的任何節點都不能互動時則會關閉。該模式下有兩種配置,ignore和autoheal。當出現對等節點數分割槽的情況,ignore不會自動處理,autoheal則可以處理這種情形

6.3 autoheal模式

在autoheal模式下,當認為發生網路分割槽時,RabbitMQ會自動決定一個獲勝的分割槽(winning),然後重啟不在這個分割槽中的節點來從網路分割槽中恢復獲勝分割槽是指客戶端連線最多的分割槽,如果連線客戶端相同,則選擇節點數較多的一個分割槽,如果節點數相同,則以節點名稱的字典順序來判斷。

autoheal模式在判定出net_tick_timeout時不做動作,要等到網路分割槽恢復時,在判定出網路分割槽之後才會有相應的動作,即重啟非獲勝分割槽中的節點。需要注意,在此模式下,如果叢集中有節點處於非執行狀態,那麼當發生網路分割槽時,將不會有任何自動處理的動作。

6.4 挑選哪種模式

  • ignore模式:發生網路分割槽時,不做任何動作,需要人工介入;
  • pause-minority模式:對於對等分割槽的處理不夠優雅,可能會關閉所有節點。一般用於非跨機架、奇數節點數的叢集中;
  • pause-if-all-down模式:對於受信任節點的選擇較為考究,尤其是在叢集中所有節點硬體配置相同的情況下。該模式可以處理對等分割槽的情形;
  • autoheal模式:可以應對各個情形下的網路分割槽。但是如果叢集中有節點處於非執行狀態,則此模式會失效。

參考資料

  1. 《RabbitMQ實戰指南》 朱忠華 著
  2. https://blog.csdn.net/weixin_34318956/article/details/90324665
  3. https://www.cnblogs.com/Jscroop/p/14382790.html