(十二)、ZooKeeper 機制

文章轉載:http://agapple.iteye.com/blog/1292129

背景

繼續前面的zookeeper學習的專題,這次主要是結合項目中遇到的一些問題,進一步學習了下zookeeper的一些內部機制。

 

針對以下幾個問題:

1. zk是否可以保證watcher事件不丟失?

2. zk的EPHEMERAL節點的自動過期時間? 

3. zk的如何保證節點數據不丟失?

 

如果你已經非常清楚這以上的幾個問題,看官們可以不用往下看了。 

persit機制

zookeeper中的persit機制主要是通過本地disk進行持久化,在本地disk上會有個memory數據對象保持同步。

 

持久化實現:

ZKDatabase

 

  • DataTree (內存樹)
  • FileTxnSnapLog (disk持久化)
  • committedLog (FileTxnSnapLog的一份內存數據cache,默認存儲500條變更記錄)

DataTree(內存樹)

zookeeper本身的數據結構就是一個樹結構

數據模型(DataTree):

 

  • DataNode (1:n)
  • data WatchManager (1:1,處理node節點的CRUD的變更事件,發送Watcher事件)
  • child WatchManager (1:1,  處理node子節點的變更事件,發送Watcher事件)
  • sessions (ephemerals)
DataNode模型:
  • parent 
  • data byte[]
  • acl(安全)
  • stat(審計信息)
  • children
整個實現相對比較簡單,就是查找一個樹節點後進行響應的操作

 

FileTxnSnapLog (disk持久化)

持久化數據分兩類: 

 

  • TxnLog (類似於mysql/oracle的binlog/redolog)
  • SnapShot (DataTree的數據鏡像)
剛開始最容易搞不清楚就是Txnlog和SnapShot的區別,SnapShot主要是定期對DataTree的數據做一個本地備份,TxnLog只是一些歷史的版本變更日誌(每次由寫事件變化,就會寫入到該日誌中)。

下面看一下,zookeeper在新節點啓動後,是否如何保證數據一致: 
  1. 首先節點啓動後,嘗試讀取本地的SnapShot log數據(zkDb.loadDataBase()),反序列化爲DataTree對象,並獲取last zxid。 
  2. follower啓動後會向leader發送自己的last zxid
  3. leader收到zxid後,對比自己當前的ZKDatabase中的last zxid
    如果當前follower的zxid在內存committedLog中,直接將內存中的committedLog提取出來進行發送,否則將當前的DataTree直接發送給follower.(不再是發送變更記錄)
  4. 數據同步完成後,follower會開始接收request請求

一致性機制

整個zk集羣在處理數據變更過程中,會是先append變更信息到Txnlog中(此時會觸發take snap操作),最後在FinalRequestProcessor中更新內存中的DataTree信息。

觸發take snap的條件:

 

if (logCount > (snapCount / 2 + randRoll)) {
randRoll = r.nextInt(snapCount/2);

 

 snapCount可以通過jvm參數zookeeper.snapCount指定,默認爲100000。 這裏zookeeper很巧妙的加兩個隨機處理,避免zk機器在同一時間點進行take snap處理,影響性能。

session機制

zookeeper會爲每個client分配一個session,類似於web服務器一樣。針對session可以有保存一些關聯數據,zookeeper裏針對session的一些關聯數據主要就是EPHEMERAL節點。

EPHEMERAL的翻譯爲短命的,技術上理解就是session關閉後,其節點即消失,和session保持相同的生命週期。

創建EPHEMERAL節點:

zookeeper.create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);

我們能用EPHEMERAL節點做啥?

  1. 分佈式集羣是否存活監控. (每個節點啓動後,註冊一個EPHEMERAL節點到zookeeper中,註冊一個Watcher獲取EPHEMERAL節點的存在情況,消失即可代表集羣節點dead)
  2. 分佈式lock (每個鎖競爭者,排隊時都在zookeeper中註冊一個EPHEMERAL節點,排隊過程中有節點dead了,zookeeper可以自動將其剔除隊列,避免出現deadlock)
  3. 替代web服務器的session,實現一個集中式session, session數據中的setAttribute都創建爲EPHEMERAL節點,session關閉後即可自動刪除,不會造成java中的"內存泄漏"
不扯遠,下面看一下zookeeper中是如何處理session的管理? 因爲用上了zookeeper,必然要考慮分佈式常見的問題
比如:
  1. zookeeper server掛了,對應的session是否會丟失?
  2. zookeeper client發生了failover後(出現了Connection Loss異常),對應的session是否會丟失?
解答:
  1. 在服務端,zookeeper中session的存儲是有進行持久化的, 具體可見perist機制的描述。 一個新節點啓動後,會從leader中同步對應的session數據
  2. 在客戶端,zookeeper在每次出現failover後(出現了Connection Loss異常),會重新帶上sessionId,sessionPasswd發起一次鏈接請求。接收到該請求的server,會返回內存中的session信息
所以,session是有可靠性保證的。

session expired機制

zookeeper中session expired機制和node數據一致性的保證原理類似,對應的follower都是受控於leader。

 

  1. follower接收到客戶端鏈接請求,就會向leader發送一次createSession的操作請求,leader收到後進行廣播通知給所有的follower/observer節點createSession
  2. leader會通過內存版的(SessionTrackerImpl),定期掃描過期的session,發送一次closeSession的請求給所有的客戶端
  3. 在2發送過程中,如果有follower接收到過期session的請求,會提交給leader進行仲裁,leader會直接返回session expired。

session expired幾個參數:

  1. 服務端: minSessionTimeout (默認值爲:tickTime * 2) , maxSessionTimeout (默認值爲 : tickTime * 20) , ticktime的默認值爲3000ms。所以session範圍爲6s ~ 60s 
  2. 客戶端: sessionTimeout, 無默認值,創建實例時必填。
一個誤區: 很多人會按照hadoop文檔中的建議,創建zookeeper客戶端時設置了sessionTimeout爲90s,而沒有改變server端的配置,默認是不會生效的。
原因: 客戶端的zookeeper實例在創建連接時,將sessionTimeout參數發送給了服務端,服務端會根據對應的minSessionTimeout/maxSessionTimeout的設置,強制修改sessionTimeout參數,也就是修改爲6s~60s返回的參數。所以服務端不一定會以客戶端的sessionTImeout做爲session expire管理的時間。
 
<span style="FONT-WEIGHT: normal">int minSessionTimeout = zk.getMinSessionTimeout();
        if (sessionTimeout < minSessionTimeout) {
            sessionTimeout = minSessionTimeout;
        }
        int maxSessionTimeout = zk.getMaxSessionTimeout();
        if (sessionTimeout > maxSessionTimeout) {
            sessionTimeout = maxSessionTimeout;
        }</span>

Watcher機制

watcher是zookeeper實現分佈式lock一個很重要的feature,在寫分佈式lock時一定要對其有所瞭解。

就會冒出如下問題:

 

  1. 什麼情況下,會觸發什麼類型的watcher?
  2. watcher信息出現failover是否會丟失?
  3. watcher信息出現session expired是否會丟失?
針對第一個問題,需要在使用中詳細閱讀下對應Zookeeper類中方法的javadoc,注意幾個點:
  • exists方法: 設置watcher時,如果對應服務端已經不存在node時,watcher是不會留在服務端,下次不會被觸發。針對這種情況需要判斷返回的stat == null來進行處理
  • getChildren方法: 和exist一樣,需要處理節點不存在時watcher不會被記錄。 還有一個點,當前的父node發生delete變化時,也可以得到觸發
  • getData方法: 和exist一樣,需要處理節點不存在時watcher不會被記錄

要了解watcher是否會丟失,必須要清楚zookeeper整套watcher機制的實現:
  • Watcher是一個本地jvm的callback,在和服務端交互過程中是不會進行傳遞的。只是會將是否有watcher的boolean變量傳遞給server端
  • 在服務端,在FinalRequestProcessor處理對應的node操作時,會根據客戶端傳遞的watcher變量,添加到對應的zkDataBase中進行持久化存儲,同時將自己NIOServerCnxn做爲一個Watcher callback,監聽服務端事件變化
  • leader通過投票通過了某次node變化請求後,通知給對應的follower,follower根據自己內存中的zkDataBase信息,發送notification信息給zookeeper 客戶端
  • zookeeper客戶端接收到notification信息後,找到對應變化path的watcher列表,挨個進行觸發回調。
整個流程圖:



可能存在的問題:
1. client向連接的server提交了watcher事件後,對應的server還未來得及提交給leader就直接出現了jvm crash,這時對應的watcher事件會丟失。(理論上正常關閉zookeeper server,不會存在該問題,需要客戶端進行重試處理)
2. client在發生一次failover時,可以自動對新的server的notification使用watcher set,可以通過設置jvm變量:zookeeper.disableAutoWatchReset進行,默認爲false。如果爲true,則不進行自動使用的行爲)
3. client出現session expired時,需要重新創建一個zookeeper client實例,此時對應的watcher set也會丟失,需要自己編碼做一些額外的處理

zookeeper異常處理

官方文檔:http://wiki.apache.%20org/hadoop/ZooKeeper/FAQ

主要處理兩個系統異常:

 

  • KeeperException.ConnectionLossException (client與其中的一臺server socket鏈接出現異常)
  • KeeperException.SessionExpiredException (client的session超過sessionTimeout爲進行任何操作)
ConnectionLossException可以通過重試進行處理,在ClientCnxn會根據你初始化ZooKeeper時傳遞的服務列表,自動嘗試下一個server節點
SessionExpiredException不能通過重試進行解決,需要應用重新new Zookeeper(),創建一個新的客戶端,包括重新初始化對應的watcher,EPHEMERAL節點等。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章