zookeeper是我在使用dubbo的時(shí)候認(rèn)識(shí)的一個(gè)中間件穆碎,它是一個(gè)分布式服務(wù)框架鼠证,當(dāng)時(shí)候涛酗,我只知道dubbo通過它可以注冊(cè)一些服務(wù)列表,然后通過服務(wù)提供者和服務(wù)消費(fèi)者互相協(xié)助纱新,完整一個(gè)分布式的服務(wù)應(yīng)用。
那zookeeper究竟是一個(gè)什么東西呢穆趴?最近脸爱,我想認(rèn)真的了解一下它到底是何方神圣。經(jīng)過閱讀網(wǎng)上的一些文章后以及官網(wǎng)的一些它的原理圖未妹。我開始有了一個(gè)大概的認(rèn)知簿废。
我就用我們項(xiàng)目案例來講解一下對(duì)zookeeper的認(rèn)知。首先络它,在以前一個(gè)web項(xiàng)目族檬,是所有的服務(wù)都是寫在一個(gè)項(xiàng)目里面的,當(dāng)需要集群的時(shí)候化戳,復(fù)制相同的項(xiàng)目代碼到其他服務(wù)器上面单料,這樣子的話代碼重復(fù),以及耦合等等各種問題迂烁,從而誕生了微服務(wù)-soa看尼。
dubbo就是微服務(wù)技術(shù)。我們?cè)赾ontroller層調(diào)用service層的時(shí)候盟步,在以前藏斩,是直接都寫在一個(gè)項(xiàng)目里面的。但現(xiàn)在需要把這兩層分離却盘,分為兩個(gè)項(xiàng)目狰域,web-server和web-service媳拴。相當(dāng)于,controller依賴的一些service,調(diào)用的時(shí)候都需要通過網(wǎng)絡(luò)傳輸來實(shí)現(xiàn)兆览。那么 web-server怎么知道調(diào)用哪一個(gè)web-service項(xiàng)目呢屈溉?沒錯(cuò),就是通過dubbo這個(gè)框架抬探。順便說說子巾,在一個(gè)項(xiàng)目里面,比如用戶小压,權(quán)限线梗,還有其他復(fù)雜業(yè)務(wù)都可以把這些分為一個(gè)一個(gè)的服務(wù),web-servcie1,web-service2.....怠益,這樣仪搔,web-server1就可以調(diào)用web-service2的服務(wù),代碼就可以解耦蜻牢,不需要重復(fù)代碼了烤咧。
說了那么久,為啥還沒說到zookeeper抢呆?好了煮嫌,現(xiàn)在來說說zookeeper在這里的作用。我們把web-server,web-service這些項(xiàng)目都統(tǒng)稱為client镀娶,每當(dāng)web-server,web-service啟動(dòng)項(xiàng)目立膛,首先都會(huì)去與zookeeper服務(wù)器進(jìn)行連接。web-server是cosumer,web-service是provider梯码,provider是把服務(wù)注冊(cè)到zoo上面去了宝泵,那到底詳細(xì)是怎么樣實(shí)現(xiàn)的。provider項(xiàng)目里面會(huì)引用zookeeper-client的api 在zookeeper上面創(chuàng)建數(shù)據(jù)轩娶。zookeeper的數(shù)據(jù)形式其實(shí)是一個(gè)樹儿奶,以/app,/app/n1文件路徑為名的 樹,每個(gè)節(jié)點(diǎn)叫znodo.
比如鳄抒,web-service有一個(gè)服務(wù)叫userService闯捎,client會(huì)發(fā)送消息到zoo上面,在zoo上面創(chuàng)建節(jié)點(diǎn), create /worker许溅, 然后在這個(gè)節(jié)點(diǎn)上面掛userSerivice的數(shù)據(jù)瓤鼻,當(dāng)然上面可以有ip,port之類的。consumer鏈接后贤重,進(jìn)行訂閱茬祷,它可以對(duì)該節(jié)點(diǎn)進(jìn)行監(jiān)聽watcher,當(dāng)數(shù)據(jù)更新后并蝗,就會(huì)通知consumer了祭犯,consumer就會(huì)知道 對(duì)應(yīng)userService的相關(guān)信息秸妥,dubbo底層可以通過netty進(jìn)行 遠(yuǎn)程調(diào)用服務(wù)。
對(duì)于單一模式沃粗,就是非常簡單了粥惧,一臺(tái)zookeeper,其他的都是服務(wù)最盅。但如果zookeeper掛掉了突雪,那豈不是整個(gè)集群都有問題了?所以就必須zookeeper集群涡贱。簡單來說就是 部署多臺(tái)zookeeper挂签,然后最好是基數(shù)臺(tái),他們會(huì)選舉Leader盼产,其他都是follower, 寫操作都會(huì)先發(fā)送到leader勺馆,操作完后戏售,進(jìn)行同步數(shù)據(jù),zoo用的是 zab協(xié)議草穆。(下面是抄人的http://www.cnblogs.com/sunddenly/p/4138580.html\)
3.2.1 廣播模式
廣播模式類似一個(gè)簡單的兩階段提交:Leader發(fā)起一個(gè)請(qǐng)求灌灾,收集選票,并且最終提交悲柱,圖3.3演示了我們協(xié)議的消息流程锋喜。我們可以簡化該兩階段提交協(xié)議,因?yàn)槲覀儾]有"aborts"的情況豌鸡。followers要么確認(rèn)Leader的Propose嘿般,要么丟棄該Leader的Propose。沒有"aborts"意味著涯冠,只要有指定數(shù)量的機(jī)器確認(rèn)了該P(yáng)ropose炉奴,而不是等待所有機(jī)器的回應(yīng)。
圖 3.3 The flow of message with protocol
廣播協(xié)議在所有的通訊過程中使用TCP的FIFO信道蛇更,通過使用該信道瞻赶,使保持有序性變得非常的容易。通過FIFO信道派任,消息被有序的deliver砸逊。只要收到的消息一被處理,其順序就會(huì)被保存下來掌逛。
Leader會(huì)廣播已經(jīng)被deliver的Proposal消息师逸。在發(fā)出一個(gè)Proposal消息前,Leader會(huì)分配給Proposal一個(gè)單調(diào)遞增的唯一id颤诀,稱之為zxid字旭。因?yàn)閆ab保證了因果有序对湃,所以遞交的消息也會(huì)按照zxid進(jìn)行排序。廣播是把Proposal封裝到消息當(dāng)中遗淳,并添加到指向Follower的輸出隊(duì)列中拍柒,通過FIFO信道發(fā)送到 Follower。當(dāng)Follower收到一個(gè)Proposal時(shí)屈暗,會(huì)將其寫入到磁盤拆讯,可以的話進(jìn)行批量寫入。一旦被寫入到磁盤媒介當(dāng)中养叛,F(xiàn)ollower就會(huì)發(fā)送一個(gè)ACK給Leader种呐。 當(dāng)Leader收到了指定數(shù)量的ACK時(shí),Leader將廣播commit消息并在本地deliver該消息弃甥。當(dāng)收到Leader發(fā)來commit消息時(shí)爽室,F(xiàn)ollower也會(huì)遞交該消息。
需要注意的是淆攻, 該簡化的兩階段提交自身并不能解決Leader故障阔墩,所以我們 添加恢復(fù)模式來解決Leader故障。
3.2.2 恢復(fù)模式
(1) 恢復(fù)階段概述
正常工作時(shí)Zab協(xié)議會(huì)一直處于廣播模式瓶珊,直到Leader故障或失去了指定數(shù)量的Followers啸箫。為了保證進(jìn)度,恢復(fù)過程中必須選舉出一個(gè)新Leader伞芹,并且最終讓所有的Server擁有一個(gè)正確的狀態(tài)忘苛。對(duì)于Leader選舉,需要一個(gè)能夠成功高幾率的保證存活的算法唱较。Leader選舉協(xié)議扎唾,不僅能夠讓一個(gè)Leader得知它是leader,并且有指定數(shù)量的Follower同意該決定南缓。如果Leader選舉階段發(fā)生錯(cuò)誤稽屏,那么Servers將不會(huì)取得進(jìn)展。最終會(huì)發(fā)生超時(shí)西乖,重新進(jìn)行Leader選舉狐榔。在我們的實(shí)現(xiàn)中,Leader選舉有兩種不同的實(shí)現(xiàn)方式获雕。如果有指定數(shù)量的Server正常運(yùn)行薄腻,快速選舉的完成只需要幾百毫秒。
(2)恢復(fù)階段的保證
該恢復(fù)過程的復(fù)雜部分是在一個(gè)給定的時(shí)間內(nèi)届案,提議沖突的絕對(duì)數(shù)量庵楷。最大數(shù)量沖突提議是一個(gè)可配置的選項(xiàng),但是默認(rèn)是1000。為了使該協(xié)議能夠即使在Leader故障的情況下也能正常運(yùn)作尽纽。我們需要做出兩條具體的保證:
①我們絕不能遺忘已經(jīng)被deliver的消息咐蚯,若一條消息在一臺(tái)機(jī)器上被deliver,那么該消息必須將在每臺(tái)機(jī)器上deliver弄贿。
②我們必須丟棄已經(jīng)被skip的消息驰徊。
(3) 保證示例
第一條:
若一條消息在一臺(tái)機(jī)器上被deliver嘹吨,那么該消息必須將在每臺(tái)機(jī)器上deliver屹耐,即使那臺(tái)機(jī)器故障了厅须。例如,出現(xiàn)了這樣一種情況:Leader發(fā)送了commit消息危尿,但在該commit消息到達(dá)其他任何機(jī)器之前呐萌,Leader發(fā)生了故障。也就是說谊娇,只有Leader自己收到了commit消息肺孤。如圖3.4中的C2。
圖 3.4 The flow of message with protocol
圖3.4是"第一條保證"(deliver消息不能忘記)的一個(gè)示例济欢。在該圖中Server1是一個(gè)Leader渠旁,我們用L1表示,Server2和Server3為Follower船逮。首先Leader發(fā)起了兩個(gè)Proposal,P1和P2粤铭,并將P1挖胃、P2發(fā)送給了Server1和Server2。然后Leader對(duì)P1發(fā)起了Commit即C1梆惯,之后又發(fā)起了一個(gè)Proposal即P3酱鸭,再后來又對(duì)P2發(fā)起了commit即C2,就在此時(shí)我們的Leader掛了垛吗。那么這時(shí)候凹髓,P3和C2這兩個(gè)消息只有Leader自己收到了。
因?yàn)長eader已經(jīng)deliver了該C2消息怯屉,client能夠在消息中看到該事務(wù)的結(jié)果蔚舀。所以該事務(wù)必須能夠在其他所有的Server中deliver,最終使得client看到了一個(gè)一致性的服務(wù)視圖锨络。
第二條:
一個(gè)被skip的消息赌躺,必須仍然需要被skip。例如羡儿,發(fā)生了這樣一種情況:Leader發(fā)送了propose消息礼患,但在該propose消息到達(dá)其他任何機(jī)器之前,Leader發(fā)生了故障。也就是說缅叠,只有Leader自己收到了propose消息悄泥。如圖3.4中的P3所示。
在圖3.4中沒有任何一個(gè)server能夠看到3號(hào)提議肤粱,所以在圖3.5中當(dāng)server 1恢復(fù)時(shí)他需要在系統(tǒng)恢復(fù)時(shí)丟棄三號(hào)提議P3弹囚。
圖3.5
在圖3.5是"第二條保證"(skip消息必須被丟棄)的一個(gè)示例。Server1掛掉以后狼犯,Server3被選舉為Leader余寥,我們用L2表示。L2中還有未被deliver的消息P1悯森、P2宋舷,所以,L2在發(fā)出新提議P10000001瓢姻、P10000002之前祝蝠,L2先將P1、P2兩個(gè)消息deliver幻碱。因此绎狭,L2先發(fā)出了兩個(gè)commit消息C1、C2褥傍,之后L2才發(fā)出了新的提議P10000001和P10000002儡嘶。
如果Server1 恢復(fù)之后再次成為了Leader,此時(shí)再次將P3在P10000001和P10000002之后deliver恍风,那么將違背順序性的保障蹦狂。
(4) 保證的實(shí)現(xiàn)
如果Leader選舉協(xié)議保證了新Leader在Quorum Server中具有最高的提議編號(hào),即Zxid最高朋贬。那么新選舉出來的leader將具有所有已deliver的消息凯楔。新選舉出來的Leader,在提出一個(gè)新消息之前锦募,首先要保證事務(wù)日志中的所有消息都由Quorum Follower已Propose并deliver摆屯。需要注意的是,我們可以讓新Leader成為一個(gè)用最高zxid來處理事務(wù)的server糠亩,來作為一個(gè)優(yōu)化虐骑。這樣,作為新被選舉出來的Leader赎线,就不必去從一組Followers中找出包含最高zxid的Followers和獲取丟失的事務(wù)富弦。
① 第一條
所有的正確啟動(dòng)的Servers,將會(huì)成為Leader或者跟隨一個(gè)Leader氛驮。Leader能夠確保它的Followers看到所有的提議腕柜,并deliver所有已經(jīng)deliver的消息。通過將新連接上的Follower所沒有見過的所有PROPOSAL進(jìn)行排隊(duì),并之后對(duì)該P(yáng)roposals的COMMIT消息進(jìn)行排隊(duì)盏缤,直到最后一個(gè)COMMIT消息砰蠢。在所有這樣的消息已經(jīng)排好隊(duì)之后,Leader將會(huì)把Follower加入到廣播列表唉铜,以便今后的提議和確認(rèn)台舱。這一條是為了保證一致性,因?yàn)槿绻粭l消息P已經(jīng)在舊Leader-Server1中deliver了潭流,即使它剛剛將消息Pdeliver之后就掛了竞惋,但是當(dāng)舊Leader-Server1重啟恢復(fù)之后,我們的Client就可以從該Server中看到該消息Pdeliver的事務(wù)灰嫉,所以為了保證每一個(gè)client都能看到一個(gè)一致性的視圖拆宛,我們需要將該消息在每個(gè)Server上deliver。
② 第二條
skip已經(jīng)Propose讼撒,但不能deliver的消息浑厚,處理起來也比較簡單。在我們的實(shí)現(xiàn)中根盒,Zxid是由64位數(shù)字組成的钳幅,低32位用作簡單計(jì)數(shù)器。高32位是一個(gè)epoch炎滞。每當(dāng)新Leader接管它時(shí)敢艰,將獲取日志中Zxid最大的epoch,新LeaderZxid的epoch位設(shè)置為epoch+1册赛,counter位設(shè)置0钠导。用epoch來標(biāo)記領(lǐng)導(dǎo)關(guān)系的改變,并要求Quorum Servers通過epoch來識(shí)別該leader,避免了多個(gè)Leader用同一個(gè)Zxid發(fā)布不同的提議击奶。
這個(gè)方案的一個(gè)優(yōu)點(diǎn)就是,我們可以skip一個(gè)失敗的領(lǐng)導(dǎo)者的實(shí)例责掏,從而加速并簡化了恢復(fù)過程柜砾。如果一臺(tái)宕機(jī)的Server重啟,并帶有未發(fā)布的Proposal换衬,那么先前的未發(fā)布的所有提議將永不會(huì)被deliver痰驱。并且它不能夠成為一個(gè)新leader,因?yàn)槿魏我环N可能的 Quorum Servers 瞳浦,都會(huì)有一個(gè)Server其Proposal 來自與一個(gè)新epoch因此它具有一個(gè)較高的zxid担映。當(dāng)Server以Follower的身份連接,領(lǐng)導(dǎo)者檢查自身最后提交的提議叫潦,該提議的epoch 為Follower的最新提議的epoch(也就是圖3.5中新Leader-Server2中deliver的C2提議)蝇完,并告訴Follower截?cái)嗍聞?wù)日志直到該epoch在新Leader中deliver的最后的Proposal即C2。在圖3.5中,當(dāng)舊Leader-Server1連接到了新leader-Server2短蜕,leader將告訴他從事務(wù)日志中清除3號(hào)提議P3氢架,具體點(diǎn)就是清除P2之后的所有提議,因?yàn)镻2之后的所有提議只有舊Leader-Server1知道朋魔,其他Server不知道岖研。
(5) Paxos與Zab
① Paxos一致性
Paxos的一致性不能達(dá)到ZooKeeper的要求,我們可以下面一個(gè)例子警检。我們假設(shè)ZK集群由三臺(tái)機(jī)器組成孙援,Server1、Server2扇雕、Server3拓售。Server1為Leader,他生成了三條Proposal洼裤,P1邻辉、P2、P3腮鞍。但是在發(fā)送完P(guān)1之后值骇,Server1就掛了。如下圖3.6所示移国。
圖 3.6 Server1為Leader
Server1掛掉之后吱瘩,Server3被選舉成為Leader,因?yàn)樵赟erver3里只有一條Proposal—P1迹缀。所以使碾,Server3在P1的基礎(chǔ)之上又發(fā)出了一條新Proposal—P2',P2'的Zxid為02祝懂。如下圖3.7所示票摇。
圖3.7 Server2成為Leader
Server2發(fā)送完P(guān)2'之后,它也掛了砚蓬。此時(shí)Server1已經(jīng)重啟恢復(fù)矢门,并再次成為了Leader。那么灰蛙,Server1將發(fā)送還沒有被deliver的Proposal—P2和P3祟剔。由于Follower-Server2中P2'的Zxid為02和Leader-Server1中P2的Zxid相等,所以P2會(huì)被拒絕摩梧。而P3物延,將會(huì)被Server2接受。如圖3.8所示仅父。
圖3.8 Server1再次成為Leader
我們分析一下Follower-Server2中的Proposal叛薯,由于P2'將P2的內(nèi)容覆蓋了浑吟。所以導(dǎo)致,Server2中的Proposal-P3無法生效案训,因?yàn)樗母腹?jié)點(diǎn)并不存在买置。
② Zab一致性
首先來分析一下,上面的示例中為什么不滿足ZooKeeper需求强霎。ZooKeeper是一個(gè)樹形結(jié)構(gòu)忿项,很多操作都要先檢查才能確定能不能執(zhí)行,比如城舞,在圖3.8中Server2有三條Proposal轩触。P1的事務(wù)是創(chuàng)建節(jié)點(diǎn)"/zk",P2'是創(chuàng)建節(jié)點(diǎn)"/c"家夺,而P3是創(chuàng)建節(jié)點(diǎn)"/a/b",由于"/a"還沒建脱柱,創(chuàng)建"a/b"就搞不定了。那么拉馋,我們就能從此看出Paxos的一致性達(dá)不到ZooKeeper一致性的要求榨为。
為了達(dá)到ZooKeeper所需要的一致性,ZooKeeper采用了Zab協(xié)議煌茴。Zab做了如下幾條保證随闺,來達(dá)到ZooKeeper要求的一致性。
(a) Zab要保證同一個(gè)leader的發(fā)起的事務(wù)要按順序被apply蔓腐,同時(shí)還要保證只有先前的leader的所有事務(wù)都被apply之后矩乐,新選的leader才能在發(fā)起事務(wù)。
(b) 一些已經(jīng)Skip的消息回论,需要仍然被Skip散罕。
我想對(duì)于第一條保證大家都能理解,它主要是為了保證每個(gè)Server的數(shù)據(jù)視圖的一致性傀蓉。我重點(diǎn)解釋一下第二條欧漱,它是如何實(shí)現(xiàn)。為了能夠?qū)崿F(xiàn)葬燎,Skip已經(jīng)被skip的消息误甚。我們?cè)赯xid中引入了epoch,如下圖所示萨蚕。每當(dāng)Leader發(fā)生變換時(shí)靶草,epoch位就加1蹄胰,counter位置0岳遥。
圖 3.9 Zxid
我們繼續(xù)使用上面的例子,看一下他是如何實(shí)現(xiàn)Zab的第二條保證的裕寨。我們假設(shè)ZK集群由三臺(tái)機(jī)器組成浩蓉,Server1派继、Server2、Server3捻艳。Server1為Leader驾窟,他生成了三條Proposal,P1认轨、P2绅络、P3。但是在發(fā)送完P(guān)1之后嘁字,Server1就掛了恩急。如下圖3.10所示。
圖 3.10 Server1為Leader
Server1掛掉之后纪蜒,Server3被選舉成為Leader衷恭,因?yàn)樵赟erver3里只有一條Proposal—P1。所以纯续,Server3在P1的基礎(chǔ)之上又發(fā)出了一條新Proposal—P2'随珠,由于Leader發(fā)生了變換,epoch要加1猬错,所以epoch由原來的0變成了1窗看,而counter要置0。那么兔魂,P2'的Zxid為10烤芦。如下圖3.11所示。
圖 3.11 Server3為Leader
Server2發(fā)送完P(guān)2'之后析校,它也掛了构罗。此時(shí)Server1已經(jīng)重啟恢復(fù),并再次成為了Leader智玻。那么遂唧,Server1將發(fā)送還沒有被deliver的Proposal—P2和P3。由于Server2中P2'的Zxid為10吊奢,而Leader-Server1中P2和P3的Zxid分別為02和03盖彭,P2'的epoch位高于P2和P3。所以此時(shí)Leader-Server1的P2和P3都會(huì)被拒絕,那么我們Zab的第二條保證也就實(shí)現(xiàn)了页滚。如圖3.12所示召边。
圖 3.12 Server1再次成為Leader
好像沒啥了,第一次寫文章裹驰,別怪我太爛隧熙,哈哈哈哈