本文重點講解ZooKeeper腦裂問題的處理辦法。ZooKeeper是用來協調(同步)分佈式進程的服務,提供了一個簡單高性能的協調內核,用戶能夠在此之上構建更多複雜的分佈式協調功能。腦裂一般會出如今集羣環境中,好比Elasticsearch、ZooKeeper集羣,而這些集羣環境有一個統一的特色,就是它們有一個大腦,好比Elasticsearch集羣中有Master節點,ZooKeeper集羣中有Leader節點。程序員
先分享一個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->一、4->一、5->二、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的一方乾脆就自我重啓,以完全釋放有可能還佔用着的那些共享資源。
總結
總結了2020面試題,這份面試題的包含的模塊分爲19個模塊,分別是: Java 基礎、容器、多線程、反射、對象拷貝、Java Web 、異常、網絡、設計模式、Spring/Spring MVC、Spring Boot/Spring Cloud、Hibernate、MyBatis、RabbitMQ、Kafka、Zookeeper、MySQL、Redis、JVM 。
關注公衆號:程序員白楠楠,獲取上述資料。