【Zookeeper】原始碼分析之Watcher機制(二)之WatchManager

leesf發表於2017-01-16

一、前言

  前面已經分析了Watcher機制中的第一部分,即在org.apache.zookeeper下的相關類,接著來分析org.apache.zookeeper.server下的WatchManager類。

二、WatchManager原始碼分析

  2.1 類的屬性 

public class WatchManager {
    // Logger
    private static final Logger LOG = LoggerFactory.getLogger(WatchManager.class);

    // watcher表
    private final HashMap<String, HashSet<Watcher>> watchTable =
        new HashMap<String, HashSet<Watcher>>();

    // watcher到節點路徑的對映
    private final HashMap<Watcher, HashSet<String>> watch2Paths =
        new HashMap<Watcher, HashSet<String>>();
}

  說明:WatcherManager類用於管理watchers和相應的觸發器。watchTable表示從節點路徑到watcher集合的對映,而watch2Paths則表示從watcher到所有節點路徑集合的對映。

  2.2 核心方法分析

  1. size方法 

    public synchronized int size(){
        int result = 0;
        for(Set<Watcher> watches : watchTable.values()) { // 遍歷watchTable所有的值集合(HashSet<Watcher>集合)
            // 每個集合大小累加
            result += watches.size();
        }
        // 返回結果
        return result;
    }

  說明:可以看到size方法是同步的,因此在多執行緒環境下是安全的,其主要作用是獲取watchTable的大小,即遍歷watchTable的值集合。

  2. addWatch方法 

    public synchronized void addWatch(String path, Watcher watcher) {
        // 根據路徑獲取對應的所有watcher
        HashSet<Watcher> list = watchTable.get(path);
        if (list == null) { // 列表為空
            // don't waste memory if there are few watches on a node
            // rehash when the 4th entry is added, doubling size thereafter
            // seems like a good compromise
            // 新生成watcher集合
            list = new HashSet<Watcher>(4);
            // 存入watcher表
            watchTable.put(path, list);
        }
        // 將watcher直接新增至watcher集合
        list.add(watcher);

        // 通過watcher獲取對應的所有路徑
        HashSet<String> paths = watch2Paths.get(watcher);
        if (paths == null) { // 路徑為空
            // cnxns typically have many watches, so use default cap here
            // 新生成hash集合
            paths = new HashSet<String>();
            // 將watcher和對應的paths新增至對映中
            watch2Paths.put(watcher, paths);
        }
        // 將路徑新增至paths集合
        paths.add(path);
    }

  說明:addWatch方法同樣是同步的,其大致流程如下

  ① 通過傳入的path(節點路徑)從watchTable獲取相應的watcher集合,進入②

  ② 判斷①中的watcher是否為空,若為空,則進入③,否則,進入④

  ③ 新生成watcher集合,並將路徑path和此集合新增至watchTable中,進入④

  ④ 將傳入的watcher新增至watcher集合,即完成了path和watcher新增至watchTable的步驟,進入⑤

  ⑤ 通過傳入的watcher從watch2Paths中獲取相應的path集合,進入⑥ 

  ⑥ 判斷path集合是否為空,若為空,則進入⑦,否則,進入⑧

  ⑦ 新生成path集合,並將watcher和paths新增至watch2Paths中,進入⑧

  ⑧ 將傳入的path(節點路徑)新增至path集合,即完成了path和watcher新增至watch2Paths的步驟。

  3. removeWatcher方法  

    public synchronized void removeWatcher(Watcher watcher) {
        // 從wach2Paths中移除watcher,並返回watcher對應的path集合
        HashSet<String> paths = watch2Paths.remove(watcher);
        if (paths == null) { // 集合為空,直接返回
            return;
        }
        for (String p : paths) { // 遍歷路徑集合
            // 從watcher表中根據路徑取出相應的watcher集合
            HashSet<Watcher> list = watchTable.get(p);
            if (list != null) { // 若集合不為空
                // 從list中移除該watcher
                list.remove(watcher);
                if (list.size() == 0) { // 移除後list為空,則從watch表中移出
                    watchTable.remove(p);
                }
            }
        }
    }

  說明:removeWatcher用作從watch2Paths和watchTable中中移除該watcher,其大致步驟如下

  ① 從watch2Paths中移除傳入的watcher,並且返回該watcher對應的路徑集合,進入②

  ② 判斷返回的路徑集合是否為空,若為空,直接返回,否則,進入③

  ③ 遍歷②中的路徑集合,對每個路徑,都從watchTable中取出與該路徑對應的watcher集合,進入④

  ④ 若③中的watcher集合不為空,則從該集合中移除watcher,並判斷移除元素後的集合大小是否為0,若為0,進入⑤

  ⑤ 從watchTable中移除路徑。

  4. triggerWatch方法

    public Set<Watcher> triggerWatch(String path, EventType type, Set<Watcher> supress) {
        // 根據事件型別、連線狀態、節點路徑建立WatchedEvent
        WatchedEvent e = new WatchedEvent(type,
                KeeperState.SyncConnected, path);
                
        // watcher集合
        HashSet<Watcher> watchers;
        synchronized (this) { // 同步塊
            // 從watcher表中移除path,並返回其對應的watcher集合
            watchers = watchTable.remove(path);
            if (watchers == null || watchers.isEmpty()) { // watcher集合為空
                if (LOG.isTraceEnabled()) { 
                    ZooTrace.logTraceMessage(LOG,
                            ZooTrace.EVENT_DELIVERY_TRACE_MASK,
                            "No watchers for " + path);
                }
                // 返回
                return null;
            }
            for (Watcher w : watchers) { // 遍歷watcher集合
                // 根據watcher從watcher表中取出路徑集合
                HashSet<String> paths = watch2Paths.get(w);
                if (paths != null) { // 路徑集合不為空
                    // 則移除路徑
                    paths.remove(path);
                }
            }
        }
        for (Watcher w : watchers) { // 遍歷watcher集合
            if (supress != null && supress.contains(w)) { // supress不為空並且包含watcher,則跳過
                continue;
            }
            // 進行處理
            w.process(e);
        }
        return watchers;
    }

  說明:該方法主要用於觸發watch事件,並對事件進行處理。其大致步驟如下

  ① 根據事件型別、連線狀態、節點路徑建立WatchedEvent,進入②

  ② 從watchTable中移除傳入的path對應的鍵值對,並且返回path對應的watcher集合,進入③

  ③ 判斷watcher集合是否為空,若為空,則之後會返回null,否則,進入④

  ④ 遍歷②中的watcher集合,對每個watcher,從watch2Paths中取出path集合,進入⑤

  ⑤ 判斷④中的path集合是否為空,若不為空,則從集合中移除傳入的path。進入⑥

  ⑥ 再次遍歷watcher集合,對每個watcher,若supress不為空並且包含了該watcher,則跳過,否則,進入⑦

  ⑦ 呼叫watcher的process方法進行相應處理,之後返回watcher集合。

  5. dumpWatches方法

    public synchronized void dumpWatches(PrintWriter pwriter, boolean byPath) {
        if (byPath) { // 控制寫入watchTable或watch2Paths
            for (Entry<String, HashSet<Watcher>> e : watchTable.entrySet()) { // 遍歷每個鍵值對
                // 寫入鍵
                pwriter.println(e.getKey());
                for (Watcher w : e.getValue()) { // 遍歷值(HashSet<Watcher>)
                    pwriter.print("\t0x");
                    pwriter.print(Long.toHexString(((ServerCnxn)w).getSessionId()));
                    pwriter.print("\n");
                }
            }
        } else {
            for (Entry<Watcher, HashSet<String>> e : watch2Paths.entrySet()) { // 遍歷每個鍵值對
                // 寫入"0x"
                pwriter.print("0x");
                pwriter.println(Long.toHexString(((ServerCnxn)e.getKey()).getSessionId()));
                for (String path : e.getValue()) { // 遍歷值(HashSet<String>)
                    // 
                    pwriter.print("\t");
                    pwriter.println(path);
                }
            }
        }
    }

  說明:dumpWatches用作將watchTable或watch2Paths寫入磁碟。

三、總結

  WatchManager類用作管理watcher、其對應的路徑以及觸發器,其方法都是針對兩個對映的操作,相對簡單,也謝謝各位園友的觀看~ 

相關文章