最近研究了一下zookeeper(后續(xù)以zk簡(jiǎn)稱(chēng)),對(duì)于一個(gè)自認(rèn)為泡在服務(wù)器領(lǐng)域多年的老油條來(lái)說(shuō),現(xiàn)在才開(kāi)始關(guān)注zk這個(gè)東西,其實(shí)有點(diǎn)晚了,但沒(méi)辦法,以前的工作經(jīng)歷讓我壓根用不到這個(gè)玩意。只是最近因?yàn)橐紤]做ledisdb的cluster方案,以及重新考慮mixer的協(xié)調(diào)管理,才讓我真正開(kāi)始嘗試去了解zk。
什么是zookeeper
根據(jù)官網(wǎng)的介紹,zookeeper是一個(gè)分布式協(xié)調(diào)服務(wù),主要用來(lái)處理分布式系統(tǒng)中各系統(tǒng)之間的協(xié)作問(wèn)題的。
其實(shí)這么說(shuō)有點(diǎn)抽象,初次接觸zk,很多人真不知道用它來(lái)干啥,你可以將它想成一個(gè)總控節(jié)點(diǎn)(當(dāng)然它能用多機(jī)實(shí)現(xiàn)自身的HA),能對(duì)所有服務(wù)進(jìn)行操作。這樣就能實(shí)現(xiàn)對(duì)整個(gè)分布式系統(tǒng)的統(tǒng)一管理。
譬如我現(xiàn)在有n臺(tái)機(jī)器,需要?jiǎng)討B(tài)更新某一個(gè)配置,一些做法可能是通過(guò)puppet或者salt將配置先分發(fā)到不同機(jī)器,然后運(yùn)行指定的reload命令。zk的做法可能是所有服務(wù)都監(jiān)聽(tīng)一個(gè)配置節(jié)點(diǎn),直接更改這個(gè)節(jié)點(diǎn)的數(shù)據(jù),然后各個(gè)服務(wù)就能收到更新消息,然后同步最新的配置,再自行reload了。
上面只是一個(gè)很簡(jiǎn)單的例子,其實(shí)通過(guò)它并不能過(guò)多的體現(xiàn)zk的優(yōu)勢(shì)(沒(méi)準(zhǔn)salt可能還更簡(jiǎn)單),但zk不光只能干這些,還能干更awesome的事情。網(wǎng)上有太多關(guān)于zk應(yīng)用場(chǎng)景一覽的文章了,這里就不詳細(xì)說(shuō)明,后續(xù)我只會(huì)說(shuō)一下自己需要用zk解決的棘手問(wèn)題。
架構(gòu)
zk使用類(lèi)paxos算法來(lái)保證其HA,每次通過(guò)選舉得到一個(gè)master用來(lái)處理client的請(qǐng)求,client可以?huà)燧d到任意一臺(tái)zk server上面,因?yàn)閜axos這種是強(qiáng)一致同步算法,所以zk能保證每一臺(tái)server上面數(shù)據(jù)都是一致的。架構(gòu)如下:
?????????????????????????????????????????????????????????????????????
??????????????????????+-------------------------------+?????????????????????????
??????????????????????|???????????????????????????????|?????????????????????????
??????????????+----+--++??????????+----+---+????????+-+--+---+??????????????????
??????????????|?server?|??????????|?server?|????????|?server?|??????????????????
??????????????|????????+----------+?master?+--------+????????|??????????????????
??????????????+--^--^--+??????????+----^---+????????+----^---+??????????????????
?????????????????|??|??????????????????|?????????????????|??????????????????????
?????????????????|??|??????????????????|?????????????????|??????????????????????
?????????????????|??|??????????????????|?????????????????|??????????????????????
???????????+-----+??+-----+????????????+------+??????????+---------+????????????
???????????|??????????????|???????????????????|????????????????????|????????????
???????????|??????????????|???????????????????|????????????????????|????????????
??????+----+---+????????+-+------+?????????+--+-----+???????????+--+-----+??????
??????|?client?|????????|?client?|?????????|?client?|???????????|?client?|??????
??????+--------+????????+--------+?????????+--------+???????????+--------+??????
Data Model
zk內(nèi)部是按照類(lèi)似文件系統(tǒng)層級(jí)方式進(jìn)行數(shù)據(jù)存儲(chǔ)的,就像這樣:
+---+
| / |
+++-+
||
||
+-------+------++----+-------+
| /app1 | | /app2 |
+-+--+--+ +---+---+
| | |
| | |
| | |
+----------++ ++---------+ +----+-----+
| /app1/p1 | | /app1/p2 | | /app2/p1 |
+----------+ +----------+ +----------+
對(duì)于任意一個(gè)節(jié)點(diǎn),我們稱(chēng)之為znode,znode有很多屬性,譬如Zxid(每次更新的事物ID)等,具體可以詳見(jiàn)zk的文檔。znode有ACL控制,我們可以很方便的設(shè)置其讀寫(xiě)權(quán)限等,但個(gè)人感覺(jué)對(duì)于內(nèi)網(wǎng)小集群來(lái)說(shuō)意義不怎么大,所以也就沒(méi)深入研究。
znode有一種Ephemeral Node,也就是臨時(shí)節(jié)點(diǎn),它是session有效的,當(dāng)session結(jié)束之后,這個(gè)node自動(dòng)刪除,所以我們可以用這種node來(lái)實(shí)現(xiàn)對(duì)服務(wù)的監(jiān)控。譬如一個(gè)服務(wù)啟動(dòng)之后就向zk掛載一個(gè)ephemeral node,如果這個(gè)服務(wù)崩潰了,那么連接斷開(kāi),session無(wú)效了,這個(gè)node就刪除了,我們也就知道該服務(wù)出了問(wèn)題。
znode還有一種Sequence Node,用來(lái)實(shí)現(xiàn)序列化的唯一節(jié)點(diǎn),我們可以通過(guò)這個(gè)功能來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單地leader服務(wù)選舉,譬如每個(gè)服務(wù)啟動(dòng)的時(shí)候都向zk注冊(cè)一個(gè)sequence node,誰(shuí)最先注冊(cè),zk給的sequence最小,這個(gè)最小的就是leader了,如果leader當(dāng)?shù)袅耍敲淳哂械诙equence node的節(jié)點(diǎn)就成為新的leader。
Znode Watch
我們可以watch一個(gè)znode,用來(lái)監(jiān)聽(tīng)對(duì)應(yīng)的消息,zk會(huì)負(fù)責(zé)通知,但只會(huì)通知一次。所以需要我們?cè)俅沃匦聎atch這個(gè)znode。那么如果再次watch之前,znode又有更新了,client不是收不到了嗎?這個(gè)就需要client不光要處理watch,同時(shí)也需要適當(dāng)?shù)闹鲃?dòng)get相關(guān)的數(shù)據(jù),這樣就能保證得到最新的消息了。也就是消息系統(tǒng)里面典型的推拉結(jié)合的方式。推只是為了提升性能,快速響應(yīng),而拉則為了更好的保證消息不丟失。
但是,我們需要注意一點(diǎn),zk并不能保證client收到消息之后同時(shí)處理,譬如配置文件更新,zk可能通知了所有client,但client并不能全部在同一個(gè)時(shí)間同時(shí)reload,所以為了處理這樣的問(wèn)題,我們需要額外的機(jī)制來(lái)保證,這個(gè)后續(xù)說(shuō)明。
watch只能應(yīng)用于data(通過(guò)get,exists函數(shù))以及children(通過(guò)getChildren函數(shù))。也就是監(jiān)控znode數(shù)據(jù)更新以及znode的子節(jié)點(diǎn)的改變。
API
zk的API時(shí)很簡(jiǎn)單的,如下:
- create
- delete
- exists
- set data
- get data
- get chilren
- sync
就跟通常的文件系統(tǒng)操作差不多,就不過(guò)多說(shuō)明了。
Example
總的來(lái)說(shuō),如果我們不深入zk的內(nèi)部實(shí)現(xiàn),譬如paxos等,zk還是很好理解的,而且使用起來(lái)很簡(jiǎn)單。通常我們需要考慮的就是用zk來(lái)干啥,而不是為了想引入一個(gè)牛的新特性而用zk。
Lock
用zk可以很方便的實(shí)現(xiàn)一個(gè)分布式lock,記得最開(kāi)始做企業(yè)群組盤(pán)的時(shí)候,我需要實(shí)現(xiàn)一個(gè)分布式lock,然后就用redis來(lái)弄了一個(gè),其實(shí)當(dāng)時(shí)就很擔(dān)心redis單點(diǎn)當(dāng)?shù)舻膯?wèn)題,如果那時(shí)候我就引入了zk,可能就沒(méi)這個(gè)擔(dān)心了。
官方文檔已經(jīng)很詳細(xì)的給出了lock的實(shí)現(xiàn)流程:
- create一個(gè)類(lèi)似path/lock-n的臨時(shí)序列節(jié)點(diǎn)
- getChilren相應(yīng)的path,注意這里千萬(wàn)不能watch,不然驚群很恐怖的
- 如果1中n是最小的,則獲取lock
- 否則,調(diào)用exists watch到上一個(gè)比自己小的節(jié)點(diǎn),譬如我現(xiàn)在n是5,我就可能watch node-4
- 如果exists失敗,表明前一個(gè)節(jié)點(diǎn)沒(méi)了,則進(jìn)入步驟2,否則等待,直到watch觸發(fā)重新進(jìn)入步驟2
Codis
最近在考慮ledisdb的cluster方案,本來(lái)也打算用proxy來(lái)解決的,然后就在想用zk來(lái)處理rebalance的問(wèn)題,結(jié)果這時(shí)候codis橫空出世,發(fā)現(xiàn)不用自己整了,于是就好好的研究了一下codis的數(shù)據(jù)遷移問(wèn)題。其實(shí)也很簡(jiǎn)單:
- config發(fā)起pre migrate action
- proxy接收到這個(gè)action之后,將對(duì)應(yīng)的slot設(shè)置為pre migrate狀態(tài),同時(shí)等待config發(fā)起migrate action
- config等待所有的proxy返回pre migrate之后,發(fā)起migrate action
- proxy收到migrate action,將對(duì)應(yīng)的slot設(shè)置為migrate狀態(tài)
上面這些,都是通過(guò)zk來(lái)完成的,這里需要關(guān)注一下為啥要有pre migrate這個(gè)狀態(tài),如果config直接發(fā)起migrate,那么zk并不能保證proxy同一時(shí)間全部更新成migrate狀態(tài),所以我們必須有一個(gè)中間狀態(tài),在這個(gè)中間狀態(tài)里面,proxy對(duì)于特定的slot不會(huì)干任何事情,只能等待config將其設(shè)置為migrate。雖然proxy對(duì)于相應(yīng)slot一段時(shí)間無(wú)法處理外部請(qǐng)求,但這個(gè)時(shí)間是很短的(不過(guò)此時(shí)config當(dāng)?shù)袅司蛻K了)。config知道所有proxy都變成pre migrate狀態(tài)之后,就可以很放心的發(fā)送migrate action了。因?yàn)檫@時(shí)候,proxy只有兩種可能,變成migrate狀態(tài),能正常工作,仍然還是pre migrate狀態(tài),不能工作,也自然不會(huì)對(duì)數(shù)據(jù)造成破壞。
其實(shí)上面也就是一個(gè)典型的2PC,雖然仍然可能有隱患,譬如config當(dāng)?shù)?,但并不?huì)對(duì)實(shí)際數(shù)據(jù)造成破壞。而且config當(dāng)?shù)袅宋覀円材芎芸熘獣圆⒅匦聠?dòng),所以問(wèn)題不大。
總結(jié)
總的來(lái)說(shuō),zk的使用還是挺簡(jiǎn)單的,只要我們知道它到底能用到什么地方,那zk就真的是分布式開(kāi)發(fā)里面一把瑞士軍刀了。不過(guò)我挺不喜歡裝java那套東西,為了zk也沒(méi)辦法,雖然go現(xiàn)在也有etcd這些類(lèi)zk的東西了,但畢竟還沒(méi)經(jīng)受過(guò)太多的考驗(yàn),所以現(xiàn)在還是老老實(shí)實(shí)的zk吧。