Zookeeper Client簡介

直接使用zk的api實現業務功能比較繁瑣。因爲要處理session loss,session expire等異常,在發生這些異常後進行重連。又因爲ZK的watcher是一次性的,如果要基於wather實現發佈/訂閱模式,還要自己包裝一下,將一次性訂閱包裝成持久訂閱。另外如果要使用抽象級別更高的功能,比如分佈式鎖,leader選舉等,還要自己額外做很多事情。這裏介紹下ZK的兩個第三方客戶端包裝小工具,可以分別解決上述小問題。

一、 zkClient
zkClient主要做了兩件事情。一件是在session loss和session expire時自動創建新的ZooKeeper實例進行重連。另一件是將一次性watcher包裝爲持久watcher。後者的具體做法是簡單的在watcher回調中,重新讀取數據的同時再註冊相同的watcher實例。

zkClient簡單的使用樣例如下:

	public static void testzkClient(final String serverList) {
		ZkClient zkClient4subChild = new ZkClient(serverList);
		zkClient4subChild.subscribeChildChanges(PATH, new IZkChildListener() {
			@Override
			public void handleChildChange(String parentPath, List currentChilds) throws Exception {
				System.out.println(prefix() + "clildren of path " + parentPath + ":" + currentChilds);
			}
		});

上面是訂閱children變化,下面是訂閱數據變化

		ZkClient zkClient4subData = new ZkClient(serverList);
		zkClient4subData.subscribeDataChanges(PATH, new IZkDataListener() {
			@Override
			public void handleDataChange(String dataPath, Object data) throws Exception {
				System.out.println(prefix() + "Data of " + dataPath + " has changed");
			}

			@Override
			public void handleDataDeleted(String dataPath) throws Exception {
				System.out.println(prefix() + dataPath + " has deleted");
			}
		});

訂閱連接狀態的變化:

		ZkClient zkClient4subStat = new ZkClient(serverList);
		zkClient4subStat.subscribeStateChanges(new IZkStateListener() {
			@Override
			public void handleNewSession() throws Exception {
				System.out.println(prefix() + "handleNewSession()");
			}

			@Override
			public void handleStateChanged(KeeperState stat) throws Exception {
				System.out.println(prefix() + "handleStateChanged,stat:" + stat);
			}
		});

下面表格列出了寫操作與ZK內部產生的事件的對應關係:

  event For “/path” event For “/path/child”
create(“/path”) EventType.NodeCreated NA
delete(“/path”) EventType.NodeDeleted NA
setData(“/path”) EventType.NodeDataChanged NA
create(“/path/child”) EventType.NodeChildrenChanged EventType.NodeCreated
delete(“/path/child”) EventType.NodeChildrenChanged EventType.NodeDeleted
setData(“/path/child”) NA EventType.NodeDataChanged

而ZK內部的寫事件與所觸發的watcher的對應關係如下:

event For “/path” defaultWatcher exists
(“/path”)
getData
(“/path”)
getChildren
(“/path”)
EventType.None
EventType.NodeCreated    
EventType.NodeDeleted   √(不正常)  
EventType.NodeDataChanged    
EventType.NodeChildrenChanged      

綜合上面兩個表,我們可以總結出各種寫操作可以觸發哪些watcher,如下表所示:

  “/path” “/path/child”
  exists getData getChildren exists getData getChildren
create(“/path”)
delete(“/path”)
setData(“/path”)
create(“/path/child”)
delete(“/path/child”)
setData(“/path/child”)

如果發生session close、authFail和invalid,那麼所有類型的wather都會被觸發

zkClient除了做了一些便捷包裝之外,對watcher使用做了一點增強。比如subscribeChildChanges實際上是通過exists和getChildren關注了兩個事件。這樣當create(“/path”)時,對應path上通過getChildren註冊的listener也會被調用。另外subscribeDataChanges實際上只是通過exists註冊了事件。因爲從上表可以看到,對於一個更新,通過exists和getData註冊的watcher要麼都會觸發,要麼都不會觸發。

zkClient地址:https://github.com/sgroschupf/zkclient
Maven工程中使用zkClient需要加的依賴:

    <dependency>
        <groupId>zkclient</groupId>
        <artifactId>zkclient</artifactId>
        <version>0.1</version>
    </dependency>

二、 menagerie

menagerie基於Zookeeper實現了java.util.concurrent包的一個分佈式版本。這個封裝是更大粒度上對各種分佈式一致性使用場景的抽象。其中最基礎和常用的是一個分佈式鎖的實現:
org.menagerie.locks.ReentrantZkLock,通過ZooKeeper的全局有序的特性和EPHEMERAL_SEQUENTIAL類型znode的支持,實現了分佈式鎖。具體做法是:不同的client上每個試圖獲得鎖的線程,都在相同的basepath下面創建一個EPHEMERAL_SEQUENTIAL的node。EPHEMERAL表示要創建的是臨時znode,創建連接斷開時會自動刪除; SEQUENTIAL表示要自動在傳入的path後面綴上一個自增的全局唯一後綴,作爲最終的path。因此對不同的請求ZK會生成不同的後綴,並分別返回帶了各自後綴的path給各個請求。因爲ZK全局有序的特性,不管client請求怎樣先後到達,在ZKServer端都會最終排好一個順序,因此自增後綴最小的那個子節點,就對應第一個到達ZK的有效請求。然後client讀取basepath下的所有子節點和ZK返回給自己的path進行比較,當發現自己創建的sequential node的後綴序號排在第一個時,就認爲自己獲得了鎖;否則的話,就認爲自己沒有獲得鎖。這時肯定是有其他併發的並且是沒有斷開的client/線程先創建了node。

基於分佈式鎖,還實現了其他業務場景,比如leader選舉:
public static void leaderElectionTest() {
ZkSessionManager zksm = new DefaultZkSessionManager(“ZK-host-ip:2181″, 5000);
LeaderElector elector = new ZkLeaderElector(“/leaderElectionTest”, zksm, Ids.OPEN_ACL_UNSAFE);
if (elector.nominateSelfForLeader()) {
System.out.println(“Try to become the leader success!”);
}
}

java.util.concurrent包下面的其他接口實現,也主要是基於ReentrantZkLock的,比如ZkHashMap實現了ConcurrentMap。具體請參見menagerie的API文檔

menagerie地址:https://github.com/openUtility/menagerie
Maven工程中使用menagerie需要加的依賴:

    <dependency>
        <groupId>org.menagerie</groupId>
        <artifactId>menagerie</artifactId>
        <version>1.1-SNAPSHOT</version>
    </dependency>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章