Zookeeper之Zookeeper底層客戶端架構實現原理(轉載)
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有一些幫助。對於文章中沒有介紹或者沒有說清楚的地方需要進一步查看源碼來解決。
Zookeeper之Zookeeper底層客戶端架構實現原理(轉載)