前文閱讀: 【ZooKeeper系列】1.ZooKeeper單機版、僞集羣和集羣環境搭建 【ZooKeeper系列】2.用Java實現ZooKeeper API的調用html
在系列的前兩篇文章中,介紹了ZooKeeper環境的搭建(包括單機版、僞集羣和集羣),對建立、刪除、修改節點等場景用命令行的方式進行了測試,讓你們對ZooKeeper環境搭建及經常使用命令行有初步的認識,也爲搭建ZooKeeper的開發環境、生產環境起到了拋磚引玉的做用。也介紹了用Java來實現API的調用,包括節點的增、刪、改、查。經過對這兩篇的學習,讓你們對ZooKeeper的使用有了初步認識,也可用於實現系列後面篇章要介紹的命名服務、集羣管理、分佈式鎖、負載均衡、分佈式隊列等。java
在前兩篇中,強調了閱讀英文文檔的重要性,也帶領你們解讀了部分官方文檔,想傳達出的理念是ZooKeeper沒有想象中的那麼難,閱讀官方文檔也沒那麼難。後面的篇章中,結合官方文檔,在實戰演練和解讀源碼的基礎上加深理解。git
上聯:說你行你就行不行也行 下聯:說不行就不行行也不行 橫批:不服不行
閱讀源碼就跟這個對聯如出一轍,就看你選上聯,仍是下聯了!
github
這一篇開始源碼環境的搭建,here we go
!apache
不少老鐵留言說很想研讀些github上的開源項目,但代碼clone下來後總出現這樣或那樣奇奇怪怪的問題,很影響學習的積極性。學習ZooKeeper的源碼尤爲如此,不少人clone代碼後,報各類錯,提示少各類包。問了下度娘ZooKeeper源碼環境,搜出來的文章真的差強人意,有些文章錯的居然很是離譜。這裏我從新搭建了一遍,也會介紹遇到的一些坑。安全
不少老鐵上來一堆猛操做,從github上下載了ZooKeeper源碼後,按常規方式導入IDEA,最後發現少各類包。起初我也是這樣弄的,覺得ZooKeeper是用Maven來構建的,仔細去了解了下ZooKeeper的版本歷史,實際上是用的Ant。現在通常用的Maven或Gradle,不多見到Ant的項目了,這裏不對Ant多作介紹。app
Ant官網地址:https://ant.apache.org/bindownload.cgi負載均衡
下載解壓後,跟配置jdk同樣配置幾個環境變量:eclipse
//修改成本身本地安裝的目錄 ANT_HOMT=D:\apache-ant-1.10.7 PATH=%ANT_HOME%/bin CLASSPATH=%ANT_HOME%/lib
配置好後,測試下Ant是否安裝成功。ant -version,獲得以下信息則表明安裝成功:socket
Apache Ant(TM) version 1.10.7 compiled on September 1 2019
Ant的安裝跟JDK的安裝和配置很是類似,這裏不作過多介紹。
源碼地址:https://github.com/apache/zookeeper
猿人谷在寫本篇文章時,releases列表裏的最新版本爲release-3.5.6
,咱們以此版原本進行源碼環境的搭建。
切換到源碼所在目錄,運行ant eclipse
將項目編譯並轉成eclipse的項目結構。 這個編譯過程會比較長,差很少等了7分鐘。若是編譯成功,會出現以下結果:
上面已經將項目編譯並轉成eclipse的項目結構,按eclipse的形式導入項目。
將源碼導入IDEA後在org.apache.zookeeper.Version
中發現不少紅色警告,很明顯少了org.apache.zookeeper.version.Info
類。 查詢源碼得知是用來發布的時候生成版本用的,咱們只是研讀源碼,又不發佈版本因此直接寫死就ok了。 即新增Info類:
package org.apache.zookeeper.version; public interface Info { int MAJOR = 3; int MINOR = 5; int MICRO = 6; String QUALIFIER = null; String REVISION_HASH = "c11b7e26bc554b8523dc929761dd28808913f091"; String BUILD_DATE = "10/08/2019 20:18 GMT"; }
針對單機版本和集羣版本,分別對應兩個啓動類:
這裏咱們只作單機版的測試。
在conf目錄裏有個zoo_sample.cfg,複製一份重命名爲zoo.cfg。
zoo.cfg裏的內容作點修改(也能夠不作修改),方便日誌查詢。dataDir和dataLogDir根據本身的狀況設定。
dataDir=E:\\02private\\1opensource\\zk\\zookeeper\\dataDir dataLogDir=E:\\02private\\1opensource\\zk\\zookeeper\\dataLogDir
運行主類 org.apache.zookeeper.server.ZooKeeperServerMain
,將zoo.cfg的完整路徑配置在Program arguments。 運行ZooKeeperServerMain
,獲得的結果以下:
Connected to the target VM, address: '127.0.0.1:0', transport: 'socket' log4j:WARN No appenders could be found for logger (org.apache.zookeeper.jmx.ManagedUtil). log4j:WARN Please initialize the log4j system properly. log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
告知日誌沒法輸出,日誌文件配置有誤。這裏須要指定日誌文件log4j.properties。 在VM options配置,即指定到conf目錄下的log4j.properties:
-Dlog4j.configuration=file:E:/02private/1opensource/zk/zookeeper/conf/log4j.properties
配置後從新運行ZooKeeperServerMain
,輸出日誌以下, 能夠得知單機版啓動成功,單機版服務端地址爲127.0.0.1:2181。
經過運行ZooKeeperServerMain
獲得的日誌,能夠得知ZooKeeper服務端已經啓動,服務的地址爲127.0.0.1:2181
。啓動客戶端來進行鏈接測試。
客戶端的啓動類爲org.apache.zookeeper.ZooKeeperMain
,進行以下配置: 即客戶端鏈接127.0.0.1:2181,獲取節點/yuanrengu
的信息。
下面帶領你們一塊兒看看客戶端啓動的源碼(org.apache.zookeeper.ZooKeeperMain
)。這裏要給你們說下我閱讀源碼的習慣,不少老鐵覺得閱讀源碼就是順着代碼看,這樣也沒啥不對,只是不少開源項目代碼量驚人,這麼個幹見解,容易注意力分散也容易看花眼。我通常是基於某個功能點,從入口開始debug跑一遍,弄清這個功能的「代碼線」,就像跑馬圈塊地兒同樣,弄清楚功能有關的代碼,瞭解參數傳遞的過程,這樣看代碼時就更有針對性,也能排除不少干擾代碼。
main裏就兩行代碼,經過debug得知args裏包含的信息就是上面咱們配置在Program arguments裏的信息:
public ZooKeeperMain(String args[]) throws IOException, InterruptedException { // 用於解析參數裏的命令行的 cl.parseOptions(args); System.out.println("Connecting to " + cl.getOption("server")); // 用於鏈接ZooKeeper服務端 connectToZK(cl.getOption("server")); }
經過下圖能夠看出,解析參數後,就嘗試鏈接127.0.0.1:2181,即ZooKeeper服務端。cl.getOption("server")獲得的就是127.0.0.1:2181。
能夠很清楚的得知解析args的過程,主要從"-server","-timeout","-r","-"這幾個維度來進行解析。
protected void connectToZK(String newHost) throws InterruptedException, IOException { // 用於判斷如今ZooKeeper鏈接是否還有效 // zk.getState().isAlive() 注意這個會話是否有效的判斷,客戶端與 Zookeeper鏈接斷開不必定會話失效 if (zk != null && zk.getState().isAlive()) { zk.close(); } // 此時newHost爲127.0.0.1:2181 host = newHost; // 判斷是否爲只讀模式,關於只讀模式的概念在前一篇文章中有介紹 boolean readOnly = cl.getOption("readonly") != null; // 用於判斷是否創建安全鏈接 if (cl.getOption("secure") != null) { System.setProperty(ZKClientConfig.SECURE_CLIENT, "true"); System.out.println("Secure connection is enabled"); } zk = new ZooKeeperAdmin(host, Integer.parseInt(cl.getOption("timeout")), new MyWatcher(), readOnly); }
ZKClientConfig.SECURE_CLIENT
已經被標註爲deprecation了:
/** * Setting this to "true" will enable encrypted client-server communication. */ @SuppressWarnings("deprecation") public static final String SECURE_CLIENT = ZooKeeper.SECURE_CLIENT;
debug查看關鍵點處的信息,能夠得知這是創建一個ZooKeeper鏈接的過程(【ZooKeeper系列】2.用Java實現ZooKeeper API的調用,這篇文章裏詳細介紹過ZooKeeper創建鏈接的過程)
下圖看看幾處關鍵信息: Integer.parseInt(cl.getOption("timeout"))爲30000。
至此完成了ZooKeeperMain main = new ZooKeeperMain(args);的整個過程。簡短點說就是:
敲黑板,重頭戲來了哦!
一塊兒來看下run()的代碼:
void run() throws CliException, IOException, InterruptedException { // cl.getCommand()獲得的是 「get」,就是上文傳進來的 if (cl.getCommand() == null) { System.out.println("Welcome to ZooKeeper!"); boolean jlinemissing = false; // only use jline if it's in the classpath try { Class<?> consoleC = Class.forName("jline.console.ConsoleReader"); Class<?> completorC = Class.forName("org.apache.zookeeper.JLineZNodeCompleter"); System.out.println("JLine support is enabled"); Object console = consoleC.getConstructor().newInstance(); Object completor = completorC.getConstructor(ZooKeeper.class).newInstance(zk); Method addCompletor = consoleC.getMethod("addCompleter", Class.forName("jline.console.completer.Completer")); addCompletor.invoke(console, completor); String line; Method readLine = consoleC.getMethod("readLine", String.class); while ((line = (String)readLine.invoke(console, getPrompt())) != null) { executeLine(line); } } catch (ClassNotFoundException e) { LOG.debug("Unable to start jline", e); jlinemissing = true; } catch (NoSuchMethodException e) { LOG.debug("Unable to start jline", e); jlinemissing = true; } catch (InvocationTargetException e) { LOG.debug("Unable to start jline", e); jlinemissing = true; } catch (IllegalAccessException e) { LOG.debug("Unable to start jline", e); jlinemissing = true; } catch (InstantiationException e) { LOG.debug("Unable to start jline", e); jlinemissing = true; } if (jlinemissing) { System.out.println("JLine support is disabled"); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String line; while ((line = br.readLine()) != null) { executeLine(line); } } } else { // 處理傳進來的參數 processCmd(cl); } System.exit(exitCode); }
經過下圖能夠看出processCmd(cl);
裏cl
包含的信息: debug到processCmd(MyCommandOptions co)
就到了決戰時刻。裏面的processZKCmd(MyCommandOptions co)
就是核心了,代碼太長,只說下processZKCmd裏的重點代碼,獲取節點/yuanrengu的信息: 由於我以前沒有建立過/yuanrengu節點,會拋異常org.apache.zookeeper.KeeperException$NoNodeException: KeeperErrorCode = NoNode for /yuanrengu
, 以下圖所示: 通過上面的步驟後exitCode爲1,執行System.exit(exitCode);退出。
至此帶領你們dubug了一遍org.apache.zookeeper.ZooKeeperMain,上面我說過,閱讀源碼幹看效果很小,只有debug纔能有助於梳理流程和思路,也能清楚參數傳遞的過程發生了什麼變化。
上面咱們介紹了源碼環境的搭建過程,運行運行主類 org.apache.zookeeper.server.ZooKeeperServerMain
啓動ZooKeeper服務端,運行org.apache.zookeeper.ZooKeeperMain
鏈接服務端。
閱讀源碼最好能動起來(debug)讀,這樣代碼纔是活的,幹看的話代碼如死水同樣,容易讓人索然無味!
每一個人操做的方式不同,有可能遇到的問題也不同,搭建過程當中遇到什麼問題,你們能夠在評論區留言。