關於集群中的"腦裂"問題,之前已經在這里詳細介紹過,下面重點說下Zookeeper腦裂問題的處理辦法。ooKeeper是用來協調(同步)分布式進程的服務,提供了一個簡單高性能的協調內核,用戶可以在此之上構建更多復雜的分布式協調功能。腦裂通常會出現在集群環境中,比如ElasticSearch、Zookeeper集群,而這些集群環境有一個統一的特點,就是它們有一個大腦,比如ElasticSearch集群中有Master節點,Zookeeper集群中有Leader節點。
一、 Zookeeper 集群節點為什么要部署成奇數
zookeeper容錯指的是:當宕掉幾個zookeeper節點服務器之后,剩下的個數必須大於宕掉的個數,也就是剩下的節點服務數必須大於n/2,這樣zookeeper集群才可以繼續使用,無論奇偶數都可以選舉leader。例如5台zookeeper節點機器最多宕掉2台,還可以繼續使用,因為剩下3台大於5/2。至於為什么最好為奇數個節點?這樣是為了以最大容錯服務器個數的條件下,能節省資源。比如,最大容錯為2的情況下,對應的zookeeper服務數,奇數為5,而偶數為6,也就是6個zookeeper服務的情況下最多能宕掉2個服務,所以從節約資源的角度看,沒必要部署6(偶數)個zookeeper服務節點。
zookeeper集群有這樣一個特性:集群中只要有過半的機器是正常工作的,那么整個集群對外就是可用的。也就是說如果有2個zookeeper節點,那么只要有1個zookeeper節點死了,那么zookeeper服務就不能用了,因為1沒有過半,所以2個zookeeper的死亡容忍度為0;同理,要是有3個zookeeper,一個死了,還剩下2個正常的,過半了,所以3個zookeeper的容忍度為1;同理也可以多列舉幾個:2->0; 3->1; 4->1; 5->2; 6->2 就會發現一個規律,2n和2n-1的容忍度是一樣的,都是n-1,所以為了更加高效,何必增加那一個不必要的zookeeper呢。所以說,根據以上可以得出結論:從資源節省的角度來考慮,zookeeper集群的節點最好要部署成奇數個!
二、 Zookeeper 集群中的"腦裂"場景說明
對於一個集群,想要提高這個集群的可用性,通常會采用多機房部署,比如現在有一個由6台zkServer所組成的一個集群,部署在了兩個機房:
正常情況下,此集群只會有一個Leader,那么如果機房之間的網絡斷了之后,兩個機房內的zkServer還是可以相互通信的,如果不考慮過半機制,那么就會出現每個機房內部都將選出一個Leader。
這就相當於原本一個集群,被分成了兩個集群,出現了兩個"大腦",這就是所謂的"腦裂"現象。對於這種情況,其實也可以看出來,原本應該是統一的一個集群對外提供服務的,現在變成了兩個集群同時對外提供服務,如果過了一會,斷了的網絡突然聯通了,那么此時就會出現問題了,兩個集群剛剛都對外提供服務了,數據該怎么合並,數據沖突怎么解決等等問題。剛剛在說明腦裂場景時有一個前提條件就是沒有考慮過半機制,所以實際上Zookeeper集群中是不會輕易出現腦裂問題的,原因在於過半機制。
zookeeper的過半機制:在領導者選舉的過程中,如果某台zkServer獲得了超過半數的選票,則此zkServer就可以成為Leader了。舉個簡單的例子:如果現在集群中有5台zkServer,那么half=5/2=2,那么也就是說,領導者選舉的過程中至少要有三台zkServer投了同一個zkServer,才會符合過半機制,才能選出來一個Leader。
那么zookeeper選舉的過程中為什么一定要有一個過半機制驗證?
因為這樣不需要等待所有zkServer都投了同一個zkServer就可以選舉出來一個Leader了,這樣比較快,所以叫快速領導者選舉算法。
zookeeper過半機制中為什么是大於,而不是大於等於?這就是更腦裂問題有關系了,比如回到上文出現腦裂問題的場景 [如上圖1]:當機房中間的網絡斷掉之后,機房1內的三台服務器會進行領導者選舉,但是此時過半機制的條件是 "節點數 > 3",也就是說至少要4台zkServer才能選出來一個Leader,所以對於機房1來說它不能選出一個Leader,同樣機房2也不能選出一個Leader,這種情況下整個集群當機房間的網絡斷掉后,整個集群將沒有Leader。而如果過半機制的條件是 "節點數 >= 3",那么機房1和機房2都會選出一個Leader,這樣就出現了腦裂。這就可以解釋為什么過半機制中是大於而不是大於等於,目的就是為了防止腦裂。
如果假設我們現在只有5台機器,也部署在兩個機房:
此時過半機制的條件是 "節點數 > 2",也就是至少要3台服務器才能選出一個Leader,此時機房件的網絡斷開了,對於機房1來說是沒有影響的,Leader依然還是Leader,對於機房2來說是選不出來Leader的,此時整個集群中只有一個Leader。因此總結得出,有了過半機制,對於一個Zookeeper集群來說,要么沒有Leader,要么只有1個Leader,這樣zookeeper也就能避免了腦裂問題。
三、 Zookeeper 集群"腦裂"問題處理
1. 什么是腦裂?
簡單點來說,腦裂(Split-Brain) 就是比如當你的 cluster 里面有兩個節點,它們都知道在這個 cluster 里需要選舉出一個 master。那么當它們兩個之間的通信完全沒有問題的時候,就會達成共識,選出其中一個作為 master。但是如果它們之間的通信出了問題,那么兩個結點都會覺得現在沒有 master,所以每個都把自己選舉成 master,於是 cluster 里面就會有兩個 master。
對於Zookeeper來說有一個很重要的問題,就是到底是根據一個什么樣的情況來判斷一個節點死亡down掉了?在分布式系統中這些都是有監控者來判斷的,但是監控者也很難判定其他的節點的狀態,唯一一個可靠的途徑就是心跳,Zookeeper也是使用心跳來判斷客戶端是否仍然活着。
使用ZooKeeper來做Leader HA基本都是同樣的方式:每個節點都嘗試注冊一個象征leader的臨時節點,其他沒有注冊成功的則成為follower,並且通過watch機制 (這里有介紹) 監控着leader所創建的臨時節點,Zookeeper通過內部心跳機制來確定leader的狀態,一旦leader出現意外Zookeeper能很快獲悉並且通知其他的follower,其他flower在之后作出相關反應,這樣就完成了一個切換,這種模式也是比較通用的模式,基本大部分都是這樣實現的。但是這里面有個很嚴重的問題,如果注意不到會導致短暫的時間內系統出現腦裂,因為心跳出現超時可能是leader掛了,但是也可能是zookeeper節點之間網絡出現了問題,導致leader假死的情況,leader其實並未死掉,但是與ZooKeeper之間的網絡出現問題導致Zookeeper認為其掛掉了然后通知其他節點進行切換,這樣follower中就有一個成為了leader,但是原本的leader並未死掉,這時候client也獲得leader切換的消息,但是仍然會有一些延時,zookeeper需要通訊需要一個一個通知,這時候整個系統就很混亂可能有一部分client已經通知到了連接到新的leader上去了,有的client仍然連接在老的leader上,如果同時有兩個client需要對leader的同一個數據更新,並且剛好這兩個client此刻分別連接在新老的leader上,就會出現很嚴重問題。
這里做下小總結:
假死:由於心跳超時(網絡原因導致的)認為leader死了,但其實leader還存活着。
腦裂:由於假死會發起新的leader選舉,選舉出一個新的leader,但舊的leader網絡又通了,導致出現了兩個leader ,有的客戶端連接到老的leader,而有的客戶端則連接到新的leader。
2. zookeeper腦裂是什么原因導致的?
主要原因是Zookeeper集群和Zookeeper client判斷超時並不能做到完全同步,也就是說可能一前一后,如果是集群先於client發現,那就會出現上面的情況。同時,在發現並切換后通知各個客戶端也有先后快慢。一般出現這種情況的幾率很小,需要leader節點與Zookeeper集群網絡斷開,但是與其他集群角色之間的網絡沒有問題,還要滿足上面那些情況,但是一旦出現就會引起很嚴重的后果,數據不一致。
3. zookeeper是如何解決"腦裂"問題的?
要解決Split-Brain腦裂的問題,一般有下面幾種種方法:
Quorums (法定人數) 方式: 比如3個節點的集群,Quorums = 2, 也就是說集群可以容忍1個節點失效,這時候還能選舉出1個lead,集群還可用。比如4個節點的集群,它的Quorums = 3,Quorums要超過3,相當於集群的容忍度還是1,如果2個節點失效,那么整個集群還是無效的。這是zookeeper防止"腦裂"默認采用的方法。
采用Redundant communications (冗余通信)方式:集群中采用多種通信方式,防止一種通信方式失效導致集群中的節點無法通信。
Fencing (共享資源) 方式:比如能看到共享資源就表示在集群中,能夠獲得共享資源的鎖的就是Leader,看不到共享資源的,就不在集群中。
仲裁機制方式。
啟動磁盤鎖定方式。
要想避免zookeeper"腦裂"情況其實也很簡單,在follower節點切換的時候不在檢查到老的leader節點出現問題后馬上切換,而是在休眠一段足夠的時間,確保老的leader已經獲知變更並且做了相關的shutdown清理工作了然后再注冊成為master就能避免這類問題了,這個休眠時間一般定義為與zookeeper定義的超時時間就夠了,但是這段時間內系統可能是不可用的,但是相對於數據不一致的后果來說還是值得的。
1、zooKeeper默認采用了Quorums這種方式來防止"腦裂"現象。即只有集群中超過半數節點投票才能選舉出Leader。這樣的方式可以確保leader的唯一性,要么選出唯一的一個leader,要么選舉失敗。在zookeeper中Quorums作用如下:
1] 集群中最少的節點數用來選舉leader保證集群可用。
2] 通知客戶端數據已經安全保存前集群中最少數量的節點數已經保存了該數據。一旦這些節點保存了該數據,客戶端將被通知已經安全保存了,可以繼續其他任務。而集群中剩余的節點將會最終也保存了該數據。
假設某個leader假死,其余的followers選舉出了一個新的leader。這時,舊的leader復活並且仍然認為自己是leader,這個時候它向其他followers發出寫請求也是會被拒絕的。因為每當新leader產生時,會生成一個epoch標號(標識當前屬於那個leader的統治時期),這個epoch是遞增的,followers如果確認了新的leader存在,知道其epoch,就會拒絕epoch小於現任leader epoch的所有請求。那有沒有follower不知道新的leader存在呢,有可能,但肯定不是大多數,否則新leader無法產生。Zookeeper的寫也遵循quorum機制,因此,得不到大多數支持的寫是無效的,舊leader即使各種認為自己是leader,依然沒有什么作用。
zookeeper除了可以采用上面默認的Quorums方式來避免出現"腦裂",還可以可采用下面的預防措施:
2、添加冗余的心跳線,例如雙線條線,盡量減少“裂腦”發生機會。
3、啟用磁盤鎖。正在服務一方鎖住共享磁盤,"裂腦"發生時,讓對方完全"搶不走"共享磁盤資源。但使用鎖磁盤也會有一個不小的問題,如果占用共享盤的一方不主動"解鎖",另一方就永遠得不到共享磁盤。現實中假如服務節點突然死機或崩潰,就不可能執行解鎖命令。后備節點也就接管不了共享資源和應用服務。於是有人在HA中設計了"智能"鎖。即正在服務的一方只在發現心跳線全部斷開(察覺不到對端)時才啟用磁盤鎖。平時就不上鎖了。
4、設置仲裁機制。例如設置參考IP(如網關IP),當心跳線完全斷開時,2個節點都各自ping一下 參考IP,不通則表明斷點就出在本端,不僅"心跳"、還兼對外"服務"的本端網絡鏈路斷了,即使啟動(或繼續)應用服務也沒有用了,那就主動放棄競爭,讓能夠ping通參考IP的一端去起服務。更保險一些,ping不通參考IP的一方干脆就自我重啟,以徹底釋放有可能還占用着的那些共享資源。