ZooKeeper是一個開源分布式協(xié)調服務插佛、分布式數(shù)據(jù)一致性解決方案杠巡。可基于ZooKeeper實現(xiàn)命名服務雇寇、集群管理氢拥、Master選舉、分布式鎖等功能锨侯。
? 高可用??
為了保證ZooKeeper的可用性嫩海,在生產環(huán)境中我們使用ZooKeeper集群模式對外提供服務,并且集群規(guī)模至少由3個ZooKeeper節(jié)點組成囚痴。
? 集群至少由3個節(jié)點組成??
ZooKeeper其實2個節(jié)點也可以組成集群并對外提供服務叁怪,但我們使用集群主要目的是為了高可用。如果2個節(jié)點組成集群深滚,其中1個節(jié)點掛了奕谭,另外ZooKeeper節(jié)點不能正常對外提供服務。因此也失去了集群的意義成箫。
如果3個節(jié)點組成集群展箱,其中1個節(jié)點掛掉后,根據(jù)ZooKeeper的Leader選舉機制是可以從另外2個節(jié)點選出一個作為Leader的蹬昌,集群可以繼續(xù)對外提供服務混驰。
? 并非節(jié)點越多越好??
節(jié)點越多,使用的資源越多
節(jié)點越多,ZooKeeper節(jié)點間花費的通訊成本越高栖榨,節(jié)點間互連的Socket也越多昆汹。影響ZooKeeper集群事務處理
節(jié)點越多,造成腦裂的可能性越大
? 集群規(guī)模為奇數(shù)??
集群規(guī)模除了考慮自身成本和資源外還要結合ZooKeeper特性考慮:
節(jié)省資源
3節(jié)點集群和4節(jié)點集群婴栽,我們選擇使用3節(jié)點集群满粗;5節(jié)點集群和6節(jié)點集群,我們選擇使用5節(jié)點集群愚争。以此類推映皆。因為生產環(huán)境為了保證高可用,3節(jié)點集群最多只允許掛1臺轰枝,4節(jié)點集群最多也只允許掛1臺(過半原則中解釋了原因)捅彻。同理5節(jié)點集群最多允許掛2臺,6節(jié)點集群最多也只允許掛2臺鞍陨。
出于對資源節(jié)省的考慮步淹,我們應該使用奇數(shù)節(jié)點來滿足相同的高可用性。
集群可用性
當集群中節(jié)點間網絡通訊出現(xiàn)問題時奇數(shù)和偶數(shù)對集群的影響
? 集群配置??
ZooKeeper集群配置至少需要2處變更:
1诚撵、增加集群配置
在{ZK_HOME}/conf/zoo.cfg中增加集群的配置缭裆,結構以server.id=ip:port1:port2為標準。
比如下面配置文件中表示由3個ZooKeeper組成的集群:
server.1=localhost:2881:3881
server.2=localhost:2882:3882
server.3=localhost:2883:3883
2寿烟、配置節(jié)點id
zoo.cfg中配置集群時需要指定server.id澈驼,這個id需要在dataDir(zoo.cfg中配置)指定的目錄中創(chuàng)建myid文件,文件內容就是當前ZooKeeper節(jié)點的id韧衣。
? 集群角色??
ZooKeeper沒有使用Master/Slave的概念盅藻,而是將集群中的節(jié)點分為了3類角色:
Leader
在一個ZooKeeper集群中,只能存在一個Leader畅铭,這個Leader是集群中事務請求唯一的調度者和處理者氏淑,所謂事務請求是指會改變集群狀態(tài)的請求;Leader根據(jù)事務ID可以保證事務處理的順序性硕噩。
如果一個集群中存在多個Leader假残,這種現(xiàn)象稱為「腦裂」。試想一下炉擅,一個集群中存在多個Leader會產生什么影響辉懒?
相當于原本一個大集群,裂出多個小集群谍失,他們之間的數(shù)據(jù)是不會相互同步的眶俩。「腦裂」后集群中的數(shù)據(jù)會變得非晨煊悖混亂颠印。
Follower
Follower角色的ZooKeeper服務只能處理非事務請求纲岭;如果接收到客戶端事務請求會將請求轉發(fā)給Leader服務器;參與Leader選舉线罕;參與Leader事務處理投票處理止潮。
Follower發(fā)現(xiàn)集群中Leader不可用時會變更自身狀態(tài),并發(fā)起Leader選舉投票钞楼,最終集群中的某個Follower會被選為Leader喇闸。
Observer
Observer與Follower很像,可以處理非事務請求询件;將事務請求轉發(fā)給Leader服務器燃乍。
與Follower不同的是,Observer不會參與Leader選舉雳殊;不會參與Leader事務處理投票橘沥。
Observer用于不影響集群事務處理能力的前提下提升集群的非事務處理能力窗轩。
? Leader選舉??
Leader在集群中是非常重要的一個角色夯秃,負責了整個事務的處理和調度,保證分布式數(shù)據(jù)一致性的關鍵所在痢艺。既然Leader在ZooKeeper集群中這么重要所以一定要保證集群在任何時候都有且僅有一個Leader存在仓洼。
如果集群中Leader不可用了,需要有一個機制來保證能從集群中找出一個最優(yōu)的服務晉升為Leader繼續(xù)處理事務和調度等一系列職責堤舒。這個過程稱為Leader選舉色建。
? 選舉機制??
ZooKeeper選舉Leader依賴下列原則并遵循優(yōu)先順序:
1、選舉投票必須在同一輪次中進行
如果Follower服務選舉輪次不同舌缤,不會采納投票箕戳。
2、數(shù)據(jù)最新的節(jié)點優(yōu)先成為Leader
數(shù)據(jù)的新舊使用事務ID判定国撵,事務ID越大認為節(jié)點數(shù)據(jù)約接近Leader的數(shù)據(jù)陵吸,自然應該成為Leader。
3介牙、比較server.id壮虫,id值大的優(yōu)先成為Leader
如果每個參與競選節(jié)點事務ID一樣,再使用server.id做比較环础。server.id是節(jié)點在集群中唯一的id囚似,myid文件中配置。
不管是在集群啟動時選舉Leader還是集群運行中重新選舉Leader线得。集群中每個Follower角色服務都是以上面的條件作為基礎推選出合適的Leader饶唤,一旦出現(xiàn)某個節(jié)點被過半推選,那么該節(jié)點晉升為Leader贯钩。
? 過半原則??
ZooKeeper集群會有很多類型投票募狂。Leader選舉投票呵晨;事務提議投票;這些投票依賴過半原則熬尺。就是說ZooKeeper認為投票結果超過了集群總數(shù)的一半摸屠,便可以安全的處理后續(xù)事務。
事務提議投票
假設有3個節(jié)點組成ZooKeeper集群粱哼,客戶端請求添加一個節(jié)點季二。Leader接到該事務請求后給所有Follower發(fā)起「創(chuàng)建節(jié)點」的提議投票。如果Leader收到了超過集群一半數(shù)量的反饋揭措,繼續(xù)給所有Follower發(fā)起commit胯舷。此時Leader認為集群過半了,就算自己掛了集群也是安全可靠的绊含。
Leader選舉投票
假設有3個節(jié)點組成ZooKeeper集群桑嘶,這時Leader掛了,需要投票選舉Leader躬充。當相同投票結果過半后Leader選出逃顶。
集群可用節(jié)點
ZooKeeper集群中每個節(jié)點有自己的角色,對于集群可用性來說必須滿足過半原則充甚。這個過半是指Leader角色 + Follower角色可用數(shù)大于集群中Leader角色 + Follower角色總數(shù)以政。
假設有5個節(jié)點組成ZooKeeper集群,一個Leader伴找、兩個Follower盈蛮、兩個Observer。當掛掉兩個Follower或掛掉一個Leader和一個Follower時集群將不可用技矮。因為Observer角色不參與任何形式的投票抖誉。
所謂過半原則算法是說票數(shù) > 集群總節(jié)點數(shù)/2。其中集群總節(jié)點數(shù)/2的計算結果會向下取整衰倦。
在ZooKeeper源代碼QuorumMaj.java中實現(xiàn)了這個算法袒炉。下面代碼片段有所縮減。
publicbooleancontainsQuorum(HashSetset){
/**?n是指集群總數(shù)?*/
inthalf?=?n?/2;
return(set.size()?>?half);
}
回過頭我們看一下奇數(shù)和偶數(shù)集群在Leader選舉的結果
所以3節(jié)點和4節(jié)點組成的集群在ZooKeeper過半原則下都最多只能掛1節(jié)點耿币,但是4比3要多浪費一個節(jié)點資源梳杏。
?場景實戰(zhàn)??
我們以兩個場景來了解集群不可用時Leader重新選舉的過程。
? 3節(jié)點集群重選Leader??
假設有3節(jié)點組成的集群淹接,分別是server.1(Follower)十性、server.2(Leader)、server.3(Follower)塑悼。此時server.2不可用了劲适。集群會產生以下變化:
1、集群不可用
因為Leader掛了厢蒜,集群不可用于事務請求了霞势。
2烹植、狀態(tài)變更
所有Follower節(jié)點變更自身狀態(tài)為LOOKING,并且變更自身投票愕贡。投票內容就是自己節(jié)點的事務ID和server.id草雕。我們以(事務ID, server.id)表示。
假設server.1的事務id是10固以,變更的自身投票就是(10, 1)墩虹;server.3的事務id是8,變更的自身投票就是(8, 3)憨琳。
3诫钓、首輪投票
將變更的投票發(fā)給集群中所有的Follower節(jié)點。server.1將(10, 1)發(fā)給集群中所有Follower篙螟,包括它自己菌湃。server.3也一樣,將(8, 3)發(fā)給所有Follower遍略。
所以server.1將收到(10, 1)和(8, 3)兩個投票惧所,server.3將收到(8, 3)和(10, 1)兩個投票。
4墅冷、投票PK
每個Follower節(jié)點除了發(fā)起投票外纯路,還接其他Follower發(fā)來的投票,并與自己的投票PK(比較兩個提議的事務ID以及server.id)寞忿,PK結果決定是否要變更自身狀態(tài)并再次投票。
對于server.1來說收到(10, 1)和(8, 3)兩個投票顶岸,與自己變更的投票比較后沒有一個比自身投票(10, 1)要大的腔彰,所以server.1維持自身投票不變。
對于server.3來說收到(10, 1)和(8, 3)兩個投票辖佣,與自身變更的投票比較后認為server.1發(fā)來的投票要比自身的投票大霹抛,所以server.3會變更自身投票并將變更后的投票發(fā)給集群中所有Follower。
5卷谈、第二輪投票
server.3將自身投票變更為(10, 1)后再次將投票發(fā)給集群中所有Follower杯拐。
對于server.1來說在第二輪收到了(10, 1)投票,server.1經過PK后繼續(xù)維持不變世蔗。
對于server.3來說在第二輪收到了(10, 1)投票端逼,因為server.3自身已變更為(10, 3)投票,所以本次也維持不變污淋。
此時server.1和server.3在投票上達成一致顶滩。
6、投票接收桶
節(jié)點接收的投票存儲在一個接收桶里寸爆,每個Follower的投票結果在桶內只記錄一次礁鲁。ZooKeeper源碼中接收桶用Map實現(xiàn)盐欺。
下面代碼片段是ZooKeeper定義的接收桶,以及向桶內寫入數(shù)據(jù)仅醇。Map.Key是Long類型冗美,用來存儲投票來源節(jié)點的server.id,Vote則是對應節(jié)點的投票信息析二。節(jié)點收到投票后會更新這個接收桶墩衙,也就是說桶里存儲了所有Follower節(jié)點的投票并且僅存最后一次的投票結果。
HashMap?recvset?=newHashMap();
recvset.put(n.sid,newVote(n.leader,?n.zxid,?n.electionEpoch,?n.peerEpoch));
7甲抖、統(tǒng)計投票
接收到投票后每次都會嘗試統(tǒng)計投票漆改,投票統(tǒng)計過半后選舉成功。
投票統(tǒng)計的數(shù)據(jù)來源于投票接收桶里的投票數(shù)據(jù)准谚,我們從頭描述這個場景挫剑,來看一下接收桶里的數(shù)據(jù)變化情況。
server.2掛了后柱衔,server.1和server.3發(fā)起第一輪投票樊破。
server.1接收到來自server.1的(10, 1)投票和來自server.3的(8, 3)投票。
server.3同樣接收到來自server.1的(10, 1)投票和來自server.3的(8, 3)投票唆铐。此時server.1和server.3接收桶里的數(shù)據(jù)是這樣的:
server.3經過PK后認為server.1的選票比自己要大哲戚,所以變更了自己的投票并重新發(fā)起投票。
server.1收到了來自server.3的(10, 1)投票;server.3收到了來自sever.3的(10, 1)投票艾岂。此時server.1和server.3接收桶里的數(shù)據(jù)變成了這樣:
基于ZooKeeper過半原則:桶內投票選舉server.1作為Leader出現(xiàn)2次顺少,滿足了過半?2 > 3/2?即 2>1。
最后sever.1節(jié)點晉升為Leader王浴,server.3變更為Follower脆炎。
?集群擴容Leader啟動時機?
ZooKeeper集群擴容需要在zoo.cfg配置文件中加入新節(jié)點。擴容流程在ZooKeeper擴容中介紹氓辣。這里我們以3節(jié)點擴容到5節(jié)點時秒裕,Leader啟動時機做一個討論。
假設目前有3個節(jié)點組成集群钞啸,分別是server.1(Follower)几蜻、server.2(Leader)、server.3(Follower)体斩,假設集群中節(jié)點事務ID相同梭稚。配置文件如下。
server.1=localhost:2881:3881
server.2=localhost:2882:3882
server.3=localhost:2883:3883
1硕勿、新節(jié)點加入集群
集群中新增server.4和server.5兩個節(jié)點哨毁,首先修改server.4和server.5的zoo.cfg配置并啟動。節(jié)點4和5在啟動后會變更自身投票狀態(tài)源武,發(fā)起一輪Leader選舉投票扼褪。server.1想幻、server.2、server.3收到投票后由于集群中已有選定Leader话浇,所以會直接反饋server.4和server.5投票結果:server.2是Leader脏毯。server.4和server.5收到投票后基于過半原則認定server.2是Leader,自身便切換為Follower幔崖。
#節(jié)點server.1食店、server.2、server.3配置
server.1=localhost:2881:3881
server.2=localhost:2882:3882
server.3=localhost:2883:3883
#節(jié)點server.4赏寇、server.5配置
server.1=localhost:2881:3881
server.2=localhost:2882:3882
server.3=localhost:2883:3883
server.4=localhost:2884:3884
server.5=localhost:2885:3885
2吉嫩、停止Leader
server.4和server.5的加入需要修改集群server.1、server.2嗅定、server.3的zoo.cfg配置并重啟自娩。但是Leader節(jié)點何時重啟是有講究的,因為Leader重啟會導致集群中Follower發(fā)起Leader重新選舉渠退。在server.4和server.5兩個新節(jié)點正常加入后忙迁,集群不會因為新節(jié)點加入變更Leader,所以目前server.2依然是Leader碎乃。
我們以一個錯誤的順序啟動姊扔,看一下集群會發(fā)生什么樣的變化绑榴。修改server.2zoo.cfg配置文件祭往,增加server.4和server.5的配置并停止server.2服務耘分。停止server.2后厂捞,Leader不存在了,集群中所有Follower會發(fā)起投票残制。當server.1和server.3發(fā)起投票時并不會將投票發(fā)給server.4和server.5椭微,因為在server.1和server.3的集群配置中不包含server.4和server.5節(jié)點允扇。相反愧怜,server.4和server.5會把選票發(fā)給集群中所有節(jié)點。也就是說對于server.1和server.3他們認為集群中只有3個節(jié)點妈拌。對于server.4和server.5他們認為集群中有5個節(jié)點拥坛。
根據(jù)過半原則,server.1和server.3很快會選出一個新Leader尘分,我們這里假設server.3晉級成為了新Leader猜惋。但是我們沒有啟動server.2的情況下,因為投票不滿足過半原則培愁,server.4和server.5會一直做投票選舉Leader的動作著摔。截止到現(xiàn)在集群中節(jié)點狀態(tài)是這樣的:
3、啟動Leader
現(xiàn)在定续,我們啟動server.2谍咆。因為server.2zoo.cfg已經是server.1到serverv.5的全量配置禾锤,在server.2啟動后會發(fā)起選舉投票,同時serverv.4和serverv.5也在不斷的發(fā)起選舉投票摹察。當server.2的選舉輪次和serverv.4與serverv.5選舉輪次對齊后恩掷,最終server.2會變更自己的狀態(tài),認定server.5是Leaader供嚎。
意想不到的事情發(fā)生了黄娘,出現(xiàn)兩個Leader:
ZooKeeper集群擴容時,如果Leader節(jié)點最后啟動就可以避免這類問題發(fā)生克滴,因為在Leader節(jié)點重啟前逼争,所有的Follower節(jié)點zoo.cfg配置已經是相同的,他們基于同一個集群配置兩兩互聯(lián)劝赔,做投票選舉誓焦。