ZooKeeper集群「腦裂」
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集群「腦裂」問題處理
什麼是腦裂?
簡單點來說,腦裂(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。
ZooKeeper腦裂是什麼原因導致的?
主要原因是 ZooKeeper 集群和 ZooKeeper client 判斷超時並不能做到完全同步,也就是說可能一前一後,如果是集群先於 client 發現,那就會出現上面的情況。同時,在發現並切換後通知各個客戶端也有先後快慢。一般出現這種情況的幾率很小,需要Leader節點與 ZooKeeper 集群網路斷開,但是與其他集群角色之間的網路沒有問題,還要滿足上面那些情況,但是一旦出現就會引起很嚴重的後果,數據不一致。
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定義的超時時間就夠了,但是這段時間內系統可能是不可用的,但是相對於數據不一致的後果來說還是值得的。
ZooKeeper 默認採用了 Quorums 這種方式來防止「腦裂」現象。即只有集群中超過半數節點投票才能選舉出 Leader。這樣的方式可以確保 Leader 的唯一性,要麼選出唯一的一個 Leader,要麼選舉失敗。在 ZooKeeper 中 Quorums 作用如下:
- 集群中最少的節點數用來選舉Leader保證集群可用。
- 通知客戶端數據已經安全保存前集群中最少數量的節點數已經保存了該數據。一旦這些節點保存了該數據,客戶端將被通知已經安全保存了,可以繼續其他任務。而集群中剩餘的節點將會最終也保存了該數據。
假設某個 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 方式來避免出現「腦裂」,還可以採用下面的預防措施:
- 添加冗餘的心跳線,例如雙線條線,盡量減少「裂腦」發生機會。
- 啟用磁碟鎖。正在服務一方鎖住共享磁碟,「裂腦」發生時,讓對方完全「搶不走」共享磁碟資源。但使用鎖磁碟也會有一個不小的問題,如果佔用共享盤的一方不主動「解鎖」,另一方就永遠得不到共享磁碟。現實中假如服務節點突然死機或崩潰,就不可能執行解鎖命令。後備節點也就接管不了共享資源和應用服務。於是有人在HA中設計了「智慧」鎖。即正在服務的一方只在發現心跳線全部斷開(察覺不到對端)時才啟用磁碟鎖。平時就不上鎖了。
- 設置仲裁機制。例如設置參考IP(如網關IP),當心跳線完全斷開時,2個節點都各自ping一下 參考IP,不通則表明斷點就出在本端,不僅」心跳」、還兼對外」服務」的本端網路鏈路斷了,即使啟動(或繼續)應用服務也沒有用了,那就主動放棄競爭,讓能夠ping通參考IP的一端去起服務。更保險一些,ping不通參考IP的一方乾脆就自我重啟,以徹底釋放有可能還佔用著的那些共享資源。