zookeeper協(xié)作方式
很多協(xié)作的原語是在很多應用之間共享的,假設需要設計一個用于協(xié)作的服務,要么需要提供一份盡可能完整的原語列表并提供每個原語的調用方式,要么需要提供一定的擴展性,以便新增不同的原語。很明顯,這樣的方式太死板了,所以zookepeer選擇了另辟蹊徑。
zookeeper并沒有直接暴露原語,而是提供了一組操作維護了一個類似文件系統(tǒng)的層級結果,系統(tǒng)中的每一級節(jié)點被稱為一個znode,node的葉子結點用來儲存信息。如圖1

master znode沒有儲存任何信息,表示還沒有選舉出主節(jié)點。
worker znode下的每個節(jié)點保存的是一個可用的從節(jié)點信息。
task節(jié)點作為父節(jié)點,其下的每個節(jié)點代表等待從節(jié)點執(zhí)行的一個任務
assign作為父節(jié)點,其下的每個節(jié)點代表分配到某個從節(jié)點的一個任務信息,當主節(jié)點為某個從節(jié)點指派一項任務,就會再assign下新增一個節(jié)點。
API
create/path data
創(chuàng)建一個名為/path的znode,并包含數(shù)據(jù)data
delete/path
刪除名為/path的znode
exist/path
檢查是否存在名為/path的節(jié)點
setData/path data
設置名為/path的znode的數(shù)據(jù)為data
getData/path
返回名為/path節(jié)點的數(shù)據(jù)信息
getChildren/path
返回所有/path節(jié)點的所有子節(jié)點列表
注意
- zk不允許局部讀取或者寫入znode信息,只能全部讀取或者替換。
- zk設計只適合讀取小規(guī)模的數(shù)據(jù),因此不適合做數(shù)據(jù)存儲。
znode類型
znode還擁有四種類型,需要在新建時指定,他們是持久節(jié)點、臨時節(jié)點、持久有序節(jié)點、臨時有序節(jié)點。
持久節(jié)點
只能通過delete刪除,不會因為會話過期或創(chuàng)建該節(jié)點的客戶端崩潰而失效。
臨時借點
和持久節(jié)點相反,隨著會話過期或者創(chuàng)建者的失效而失效。一般可以用來存儲應用狀態(tài)方面的信息。臨時借點也可以被客戶端主動刪除。臨時節(jié)點不能有子節(jié)點。
有序節(jié)點
當客戶端創(chuàng)建一個有序節(jié)點,zk會在其名稱后增加一個單調遞增的序號,例如/task/task-1。通過有序節(jié)點可以創(chuàng)建具有唯一名稱的zode,同時也可以看出其創(chuàng)建順序。
監(jiān)視與通知
如果客戶端需要輪詢訪問服務端來發(fā)現(xiàn)znode的數(shù)據(jù)變化青康,這樣就恨不高效,而且沒有必要。像下圖那樣,多次請求返回的都是空集。所以需要通過通知機制來告知客戶端數(shù)據(jù)的變化。

通知機制是單次觸發(fā)操作,客戶端在接受變化時需要重新設置監(jiān)聽器,否則會錯過之后的變更。另外需要在設置新的監(jiān)聽器前獲取節(jié)點的狀態(tài),否則有可能在設置新的watcher之前數(shù)據(jù)已經(jīng)被別的客戶端修改,從而錯失變更。
zk會保證對同一個znode的操作總是先通知客戶端再變更數(shù)據(jù),如果客戶端對一個節(jié)點設置了watcher,然后連續(xù)發(fā)生了兩次更新。那么第一次更新后客戶端會在觀察第二次變更前就收到通知。

版本
每個znode都有一個版本號,在數(shù)據(jù)發(fā)生變化是時會自增,用來防止并發(fā)操作可能帶來的不一致性。例如下圖中的例子,如果c1寫入數(shù)據(jù)時另一個客戶端同時對數(shù)據(jù)做出了改變,那么c1的版本號就會過期,設置也就不會成功。

zookeeper架構
zk服務端運行與兩種模式下,獨立模式與仲裁模式。獨立模式無法進行狀態(tài)復制,只有一臺單獨的服務器。仲裁模式下有一組服務器,他們之間可能進行狀態(tài)的復制,并同時服務于客戶端的請求。下圖表示看zk服務端和客戶端之間的關系。

zookeeper仲裁
在仲裁模式下,集群間會進行狀態(tài)復制,如果讓客戶端等待每個服務器復制完成會產(chǎn)生比較高的延遲,所以zookeeper保證只要完成的服務數(shù)量達到一個允許運行的最小數(shù)目即可。其他的服務器最終也會同步完成,不影響客戶端繼續(xù)進行。
然而如何確定這個最小數(shù)目呢,一般來說需要符合多數(shù)原則,就是允許崩潰的服務數(shù)量f小于服務器數(shù)量的一半。比如一共有5臺服務器,最多允許2臺崩潰,那這個最小數(shù)目就是5-2=3。所以服務器的數(shù)量最好是個奇數(shù),否則會使集群更加脆弱。比如一共有4臺服務器,那最多允許1臺發(fā)送故障,最小數(shù)目是3,這就導致數(shù)據(jù)可能無法持久化,就需要更多的確認操作。
會話
客戶端提交給zk的所有操作都建立在一個會話上,當一個會話終止,這個會話創(chuàng)建的臨時節(jié)點也會消失。
zk客戶端初始連接到集群中的一個服務器貨一個單獨的服務器,通過TCP進行通信,如果這個節(jié)點因為崩潰等原因無法繼續(xù)通信,會話就可能轉移到另一臺服務器上,這個過程對客戶端來說是透明的。
會話提供順序保證,同一個會話以FIFO方式順序執(zhí)行,但是加入一個客戶端擁有多個并發(fā)的會話,跨會話間的FIFO未必能夠保持。例如:
客戶端建立一個會話,異步創(chuàng)建了/task和/worker
第一個會話過期
客戶端新建另一個會話,創(chuàng)建/assign
以上的情況可能只有task和assign執(zhí)行成功,因為異步調用時跨會話未必能夠按照FIFO的順序執(zhí)行。