ZooKeeper解惑

作者: 逖靖寒  來源: 博客園  發布時間: 2011-01-03 22:06  閱讀: 4755 次  推薦: 1   原文鏈接   [收藏]  

  今年年初的時候,寫了一篇ZooKeeper的入門文章《初識ZooKeeper》,一直到這一周,才有時間將ZooKeeper整個源碼通讀了一遍。不能說完全理解了ZooKeeper的工作原理與細節,但是之前心中一直關于ZooKeeper的疑問都得到了解釋。

  現在網上關于ZooKeeper的文章很多,有介紹Leader選舉算法的,有介紹ZooKeeper Server內部原理的,還有介紹ZooKeeper Client的。本文不打算再寫類似的內容,而專注與解答讀者對ZooKeeper的相關疑問。

  ZooKeeper在客戶端究竟做了什么事情

  使用過ZooKeeper的讀者都知道,初始化客戶端的代碼如下:

System.out.println("Starting ZK:");
zk = new ZooKeeper(address, 3000, this);
System.out.println("Finished starting ZK: " + zk);

  完成客戶段的初始化之后,就可以對ZooKeeper進行相應的操作了:

if (zk != null) {
    try {
        Stat s = zk.exists(root, false);
        if (s == null) {
            zk.create(root, new byte[0], Ids.OPEN_ACL_UNSAFE,
                    CreateMode.PERSISTENT);
        }
    } catch (KeeperException e) {
        System.out
                .println("Keeper exception when instantiating queue: "
                        + e.toString());
    } catch (InterruptedException e) {
        System.out.println("Interrupted exception");
    }
}

  雖然上面的代碼看起來簡單明了,但是ZooKeeper的客戶端在后臺默默做了許多事情:

    1 與ZooKeeper服務端進行通信,包括:連接,發送消息,接受消息。

    2 發送心跳信息,保持與ZooKeeper服務端的有效連接與Session的有效性。

    3 錯誤處理,如果客戶端當前連接的ZooKeeper服務端失效,自動切換到另一臺有效的ZooKeeper服務端。

    4 管理Watcher,處理異常調用和Watcher。

  Watcher的事件通知機制是如何實現的

  看過Google的分布式鎖機制Chubby論文會發現,ZooKeeper中多了一個事件訂閱機制:Watcher。那么Watcher內部究竟是如何實現的呢?其實,在ZooKeeper客戶端中,有一個成員變量(ZKWatchManager)專門負責管理所有的Watcher,當用戶使用如下代碼時:

List<String> list = zk.getChildren(path, watcher);

  ZooKeeper會將這個Watcher存儲在ZKWatchManager中,同時通知ZooKeeper服務器記錄該Client對應的Session中的Path下注冊的事件類型。當ZooKeeper服務器發生了指定的事件后,ZooKeeper服務器將通知ZooKeeper客戶端,ZooKeeper客戶端再從ZKWatchManager中找到對應的回調函數,并予以執行。

  整個過程中,客戶端存儲事件的信息和Watcher的執行邏輯,服務端只存儲事件的信息。

  如何用好ZooKeeper客戶端

  每實例化一個ZooKeeper客戶端,就開啟了一個Session。ZooKeeper客戶端是線程安全的,也可以認為它實現了連接池。

  因此,每一個應用只需要實例化一個ZooKeeper客戶端即可,同一個ZooKeeper客戶端實例可以在不同的線程中使用。除非你想同一個應用中開啟多個Session,使用不同的Watcher,在這種情況下,才需要實例化多個ZooKeeper客戶端。

  ZooKeeper是否對ZNode有大小限制

  如果你仔細看過ZooKeeper的文檔,會發現文檔中對ZNode的大小做了限制,最大不能超過1M。這個1M的大小限制在ZooKeeper的客戶端和服務端都有限制:

客戶端:

packetLen = Integer.getInteger("jute.maxbuffer", 4096 * 1024);

int len = incomingBuffer.getInt();
if (len < 0 || len >= packetLen) {
	throw new IOException("Packet len" + len + " is out of range!");
}

服務端:

static public final int maxBuffer = determineMaxBuffer();
private static int determineMaxBuffer() {
    String maxBufferString = System.getProperty("jute.maxbuffer");
    try {
        return Integer.parseInt(maxBufferString);
    } catch(Exception e) {
        return 0xfffff;
    }
    
}

if (len < 0 || len > maxBuffer) {
    throw new IOException("Unreasonable length = " + len);
}

  可以看出,ZooKeeper確實對數據的大小有限制,默認就是1M,如果希望傳輸超過1M的數據,可以修改環境變量“jute.maxbuffer”即可。

  為什么要限制ZooKeeper中ZNode的大小

  ZooKeeper是一套高吞吐量的系統,為了提高系統的讀取速度,ZooKeeper不允許從文件中讀取需要的數據,而是直接從內存中查找。還句話說,ZooKeeper集群中每一臺服務器都包含全量的數據,并且這些數據都會加載到內存中。同時ZNode的數據并支持Append操作,全部都是Replace。

  所以從上面分析可以看出,如果ZNode的過大,那么讀寫某一個ZNode將造成不確定的延時;同時ZNode過大,將過快地耗盡ZooKeeper服務器的內存。這也是為什么ZooKeeper不適合存儲大量的數據的原因。

  如何提升ZooKeeper集群的性能

  我們說性能,可以從兩個方面去考慮:寫入的性能與讀取的性能。由于ZooKeeper的寫入首先需要通過Leader,然后這個寫入的消息需要傳播到半數以上的Fellower通過才能完成整個寫入。所以整個集群寫入的性能無法通過增加服務器的數量達到目的,相反,整個集群中Fellower數量越多,整個集群寫入的性能越差。

  ZooKeeper集群中的每一臺服務器都可以提供數據的讀取服務,所以整個集群中服務器的數量越多,讀取的性能就越好。但是Fellower增加又會降低整個集群的寫入性能。為了避免這個問題,可以將ZooKeeper集群中部分服務器指定為Observer。

  更多關于ZooKeeper的文章請參考:http://www.cnblogs.com/gpcuster/tag/ZooKeeper/

1
0
 
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()