Zookeeper全解析——Client端

Zookeeper的Client直接與用戶打交道,是我們使用Zookeeper的interface。瞭解ZK Client的結構和工作原理有利於我們合理的使用ZK,並能在使用中更早的發現問題。本文將在研究源碼的技術上講述ZK Client的工作原理及內部工作機制。

在看完ZK Client的大致架構以後我希望能有一種簡單的方式描述ZK Client的基本結構,想來想去我覺得還是圖片比較能反映情況,於是我畫了這張大致的結構圖:

 

我想既然我畫了這張圖,就讓我們從這張圖開始講起吧。

 

模塊:

我們可以認爲ZK的Client由三個主要模塊組成:Zookeeper, WatcherManager, ClientCnxn

Zookeeper是ZK Client端的真正接口,用戶可以操作的最主要的類,當用戶創建一個Zookeeper實例以後,幾乎所有的操作都被這個實例包辦了,用戶不用關心怎麼連接到Server,Watcher什麼時候被觸發等等令人傷神的問題。

WatcherManager,顧名思義,它是用來管理Watcher的,Watcher是ZK的一大特色功能,允許多個Client對一個或多個ZNode進行監控,當ZNode有變化時能夠通知到監控這個ZNode的各個Client。我們把一個ZK Client簡單看成一個Zookeeper實例,那麼這個實例內部的WatcherManager就管理了ZK Client綁定的所有Watcher。

ClientCnxn是管理所有網絡IO的模塊,所有和ZK Server交互的信息和數據都經過這個模塊,包括給ZK Server發送Request,從ZK Server接受Response,以及從ZK Server接受Watcher Event。ClientCnxn完全管理了網絡,從外部看來網絡操作是透明的。

線程:

每當我們創建一個Zookeeper實例的時候,會有兩個線程被創建:SendThread和EventThread。所以當我們使用ZK Client端的時候應該儘量只創建一個Zookeeper實例並反覆使用。大量的創建銷燬Zookeeper實例不僅會反覆的創建和銷燬線程,而且會在Server端創建大量的Session。

SendThread是真正處理網絡IO的線程,所有通過網絡發送和接受的數據包都在這個線程中處理。這個線程的主體是一個while循環:

複製代碼
    while (zooKeeper.state.isAlive()) {
        try {
            if (sockKey == null) {
            // don’t re-establish connection if we are closing
                if (closing) {
                    break;
                }
                startConnect();
                lastSend = now;
                lastHeard = now;
            }
            … ….
            selector.select(to);
            Set<SelectionKey> selected;
            synchronized (this) {
                selected = selector.selectedKeys();
            }
            // Everything below and until we get back to the select is
            // non blocking, so time is effectively a constant. That is
            // Why we just have to do this once, here
            now = System.currentTimeMillis();
            for (SelectionKey k : selected) {
                … …
                if (doIO()) {
                    lastHeard = now;
                }
                … …
            }
        }
        catch() {
            … …
        }
    }
複製代碼

 

這裏用了java的nio功能,當selector偵測到事件發生的時候就會觸發一次循環,主要的操作會在doIO()裏面完成:

複製代碼
    boolean doIO() throws InterruptedException, IOException {
        boolean packetReceived = false;
        SocketChannel sock = (SocketChannel) sockKey.channel();
        if (sock == null) {
            throw new IOException(“Socket is null!”);
        }
        if (sockKey.isReadable()) {
            … …
        }
         
        if (sockKey.isWritable()) {
        … …
        }

        if (outgoingQueue.isEmpty()) {
            disableWrite();
        } else {
            enableWrite();
        }
        return packetReceived;
    }
複製代碼

 

 

這個過程大概是這樣的:

1. 如果有數據可讀,則讀取數據包,如果數據包是先前發出去的Request的Response,那麼這個數據包一定在Pending Queue裏面。將它從Pending Queue裏面移走,並將此信息添加到Waiting Event Queue 裏面,如果數據包是一個Watcher Event,將此信息添加到Waiting Event Queue裏面。

2. 如果OutgoingQueue裏面有數據需要發送,則發送數據包並把數據包從Outgoing Queue移至Pending Queue,意思是數據我已經發出去了,但還要等待Server端的回覆,所以這個請求現在是Pending 的狀態。

另外一個線程EventThread是用來處理Event的。前面提到SendThread從Server收到數據的時候會把一些信息添加到Event Thread裏面,比如Finish Event和Watcher Event。EventThread就是專門用來處理這些Event的,收到Finish Event的時候會把相對應的Package置成Finish狀態,這樣等待結果的Client函數就能得以返回。收到Watcher Event的時候會聯繫WatcherManager找到相對應的Watcher,從WatcherManager裏面移除這個Watcher(因爲每個Watcher只會被通知一次) 並回調Watcher的process函數。所以所有Watcher的process函數是運行在EventThread裏面的。

保持連接:

到目前爲止應該已經大概介紹了ZK Client端的大致結構和處理流程。還剩下一個問題就是當網絡出問題時ZK Client是如何處理的。其實這個過程並不複雜,大概是執行以下步驟:

1. 網絡發生故障,網絡操作拋出的異常被捕獲。

2. 確認網絡操作失敗,清除當前與Server相關的網絡資源,包括Socket等等。

3. 在Server列表中逐個嘗試鏈接Server。

這個過程從外界看來是透明的,外界並不會覺察到ZK Client已經悄悄地更換了一個連接的Server。

好了,對於ZK Client的介紹大概就這麼多了,希望這樣的介紹對於大家學習和使用Zookeeper有一些幫助。對於文章中沒有介紹或者沒有說清楚的地方需要進一步查看源碼來解決。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章