最近研究了一下zookeeper(后續(xù)以zk簡稱),對于一個自認為泡在服務器領域多年的老油條來說伟阔,現(xiàn)在才開始關注zk這個東西,其實有點晚了蒸痹,但沒辦法抡笼,以前的工作經(jīng)歷讓我壓根用不到這個玩意推姻。只是最近因為要考慮做ledisdb的cluster方案校翔,以及重新考慮mixer的協(xié)調(diào)管理,才讓我真正開始嘗試去了解zk饲嗽。
什么是zookeeper
根據(jù)官網(wǎng)的介紹,zookeeper是一個分布式協(xié)調(diào)服務沉馆,主要用來處理分布式系統(tǒng)中各系統(tǒng)之間的協(xié)作問題的缨叫。
其實這么說有點抽象渣叛,初次接觸zk丈秩,很多人真不知道用它來干啥,你可以將它想成一個總控節(jié)點(當然它能用多機實現(xiàn)自身的HA)淳衙,能對所有服務進行操作蘑秽。這樣就能實現(xiàn)對整個分布式系統(tǒng)的統(tǒng)一管理饺著。
譬如我現(xiàn)在有n臺機器,需要動態(tài)更新某一個配置肠牲,一些做法可能是通過puppet或者salt將配置先分發(fā)到不同機器幼衰,然后運行指定的reload命令。zk的做法可能是所有服務都監(jiān)聽一個配置節(jié)點缀雳,直接更改這個節(jié)點的數(shù)據(jù)渡嚣,然后各個服務就能收到更新消息,然后同步最新的配置肥印,再自行reload了严拒。
上面只是一個很簡單的例子,其實通過它并不能過多的體現(xiàn)zk的優(yōu)勢(沒準salt可能還更簡單)竖独,但zk不光只能干這些,還能干更awesome的事情挤牛。網(wǎng)上有太多關于zk應用場景一覽的文章了莹痢,這里就不詳細說明,后續(xù)我只會說一下自己需要用zk解決的棘手問題墓赴。
架構
zk使用類paxos算法來保證其HA竞膳,每次通過選舉得到一個master用來處理client的請求,client可以掛載到任意一臺zk server上面诫硕,因為paxos這種是強一致同步算法坦辟,所以zk能保證每一臺server上面數(shù)據(jù)都是一致的。架構如下:
?????????????????????????????????????????????????????????????????????
??????????????????????+-------------------------------+?????????????????????????
??????????????????????|???????????????????????????????|?????????????????????????
??????????????+----+--++??????????+----+---+????????+-+--+---+??????????????????
??????????????|?server?|??????????|?server?|????????|?server?|??????????????????
??????????????|????????+----------+?master?+--------+????????|??????????????????
??????????????+--^--^--+??????????+----^---+????????+----^---+??????????????????
?????????????????|??|??????????????????|?????????????????|??????????????????????
?????????????????|??|??????????????????|?????????????????|??????????????????????
?????????????????|??|??????????????????|?????????????????|??????????????????????
???????????+-----+??+-----+????????????+------+??????????+---------+????????????
???????????|??????????????|???????????????????|????????????????????|????????????
???????????|??????????????|???????????????????|????????????????????|????????????
??????+----+---+????????+-+------+?????????+--+-----+???????????+--+-----+??????
??????|?client?|????????|?client?|?????????|?client?|???????????|?client?|??????
??????+--------+????????+--------+?????????+--------+???????????+--------+??????
Data Model
zk內(nèi)部是按照類似文件系統(tǒng)層級方式進行數(shù)據(jù)存儲的章办,就像這樣:
+---+
| / |
+++-+
||
||
+-------+------++----+-------+
| /app1 | | /app2 |
+-+--+--+ +---+---+
| | |
| | |
| | |
+----------++ ++---------+ +----+-----+
| /app1/p1 | | /app1/p2 | | /app2/p1 |
+----------+ +----------+ +----------+
對于任意一個節(jié)點锉走,我們稱之為znode,znode有很多屬性藕届,譬如Zxid
(每次更新的事物ID)等挪蹭,具體可以詳見zk的文檔。znode有ACL控制休偶,我們可以很方便的設置其讀寫權限等梁厉,但個人感覺對于內(nèi)網(wǎng)小集群來說意義不怎么大,所以也就沒深入研究踏兜。
znode有一種Ephemeral Node词顾,也就是臨時節(jié)點,它是session有效的碱妆,當session結束之后肉盹,這個node自動刪除,所以我們可以用這種node來實現(xiàn)對服務的監(jiān)控山橄。譬如一個服務啟動之后就向zk掛載一個ephemeral node垮媒,如果這個服務崩潰了舍悯,那么連接斷開,session無效了睡雇,這個node就刪除了萌衬,我們也就知道該服務出了問題。
znode還有一種Sequence Node它抱,用來實現(xiàn)序列化的唯一節(jié)點秕豫,我們可以通過這個功能來實現(xiàn)一個簡單地leader服務選舉,譬如每個服務啟動的時候都向zk注冊一個sequence node观蓄,誰最先注冊混移,zk給的sequence最小,這個最小的就是leader了侮穿,如果leader當?shù)袅烁杈叮敲淳哂械诙equence node的節(jié)點就成為新的leader。
Znode Watch
我們可以watch一個znode亲茅,用來監(jiān)聽對應的消息回铛,zk會負責通知,但只會通知一次克锣。所以需要我們再次重新watch這個znode茵肃。那么如果再次watch之前,znode又有更新了袭祟,client不是收不到了嗎验残?這個就需要client不光要處理watch,同時也需要適當?shù)闹鲃觛et相關的數(shù)據(jù)巾乳,這樣就能保證得到最新的消息了您没。也就是消息系統(tǒng)里面典型的推拉結合的方式。推只是為了提升性能胆绊,快速響應紊婉,而拉則為了更好的保證消息不丟失。
但是辑舷,我們需要注意一點喻犁,zk并不能保證client收到消息之后同時處理,譬如配置文件更新何缓,zk可能通知了所有client肢础,但client并不能全部在同一個時間同時reload,所以為了處理這樣的問題碌廓,我們需要額外的機制來保證传轰,這個后續(xù)說明。
watch只能應用于data(通過get谷婆,exists函數(shù))以及children(通過getChildren函數(shù))慨蛙。也就是監(jiān)控znode數(shù)據(jù)更新以及znode的子節(jié)點的改變辽聊。
API
zk的API時很簡單的,如下:
- create
- delete
- exists
- set data
- get data
- get chilren
- sync
就跟通常的文件系統(tǒng)操作差不多期贫,就不過多說明了跟匆。
Example
總的來說,如果我們不深入zk的內(nèi)部實現(xiàn)通砍,譬如paxos等玛臂,zk還是很好理解的,而且使用起來很簡單封孙。通常我們需要考慮的就是用zk來干啥迹冤,而不是為了想引入一個牛的新特性而用zk。
Lock
用zk可以很方便的實現(xiàn)一個分布式lock虎忌,記得最開始做企業(yè)群組盤的時候泡徙,我需要實現(xiàn)一個分布式lock,然后就用redis來弄了一個膜蠢,其實當時就很擔心redis單點當?shù)舻膯栴}锋勺,如果那時候我就引入了zk,可能就沒這個擔心了狡蝶。
官方文檔已經(jīng)很詳細的給出了lock的實現(xiàn)流程:
- create一個類似path/lock-n的臨時序列節(jié)點
- getChilren相應的path,注意這里千萬不能watch贮勃,不然驚群很恐怖的
- 如果1中n是最小的贪惹,則獲取lock
- 否則,調(diào)用exists watch到上一個比自己小的節(jié)點寂嘉,譬如我現(xiàn)在n是5奏瞬,我就可能watch node-4
- 如果exists失敗,表明前一個節(jié)點沒了泉孩,則進入步驟2硼端,否則等待,直到watch觸發(fā)重新進入步驟2
Codis
最近在考慮ledisdb的cluster方案寓搬,本來也打算用proxy來解決的珍昨,然后就在想用zk來處理rebalance的問題,結果這時候codis橫空出世句喷,發(fā)現(xiàn)不用自己整了镣典,于是就好好的研究了一下codis的數(shù)據(jù)遷移問題。其實也很簡單:
- config發(fā)起pre migrate action
- proxy接收到這個action之后唾琼,將對應的slot設置為pre migrate狀態(tài)兄春,同時等待config發(fā)起migrate action
- config等待所有的proxy返回pre migrate之后,發(fā)起migrate action
- proxy收到migrate action锡溯,將對應的slot設置為migrate狀態(tài)
上面這些赶舆,都是通過zk來完成的哑姚,這里需要關注一下為啥要有pre migrate這個狀態(tài),如果config直接發(fā)起migrate芜茵,那么zk并不能保證proxy同一時間全部更新成migrate狀態(tài)叙量,所以我們必須有一個中間狀態(tài),在這個中間狀態(tài)里面夕晓,proxy對于特定的slot不會干任何事情宛乃,只能等待config將其設置為migrate。雖然proxy對于相應slot一段時間無法處理外部請求蒸辆,但這個時間是很短的(不過此時config當?shù)袅司蛻K了)征炼。config知道所有proxy都變成pre migrate狀態(tài)之后,就可以很放心的發(fā)送migrate action了躬贡。因為這時候谆奥,proxy只有兩種可能,變成migrate狀態(tài)拂玻,能正常工作酸些,仍然還是pre migrate狀態(tài),不能工作檐蚜,也自然不會對數(shù)據(jù)造成破壞魄懂。
其實上面也就是一個典型的2PC,雖然仍然可能有隱患闯第,譬如config當?shù)羰欣酰⒉粫嶋H數(shù)據(jù)造成破壞。而且config當?shù)袅宋覀円材芎芸熘獣圆⒅匦聠涌榷蹋詥栴}不大填帽。
總結
總的來說,zk的使用還是挺簡單的咙好,只要我們知道它到底能用到什么地方篡腌,那zk就真的是分布式開發(fā)里面一把瑞士軍刀了。不過我挺不喜歡裝java那套東西勾效,為了zk也沒辦法嘹悼,雖然go現(xiàn)在也有etcd這些類zk的東西了,但畢竟還沒經(jīng)受過太多的考驗层宫,所以現(xiàn)在還是老老實實的zk吧绘迁。