文章目錄
前文閱讀:
【ZooKeeper系列】1.ZooKeeper單機版、僞集羣和集羣環境搭建
【ZooKeeper系列】2.用Java實現ZooKeeper API的調用
在系列的前兩篇文章中,介紹了ZooKeeper環境的搭建(包括單機版、僞集羣和集羣),對創建、刪除、修改節點等場景用命令行的方式進行了測試,讓大家對ZooKeeper環境搭建及常用命令行有初步的認識,也爲搭建ZooKeeper的開發環境、生產環境起到了拋磚引玉的作用。也介紹了用Java來實現API的調用,包括節點的增、刪、改、查。通過對這兩篇的學習,讓大家對ZooKeeper的使用有了初步認識,也可用於實現系列後面篇章要介紹的命名服務、集羣管理、分佈式鎖、負載均衡、分佈式隊列等。
在前兩篇中,強調了閱讀英文文檔的重要性,也帶領大家解讀了部分官方文檔,想傳達出的理念是ZooKeeper沒有想象中的那麼難,閱讀官方文檔也沒那麼難。後面的篇章中,結合官方文檔,在實戰演練和解讀源碼的基礎上加深理解。
上聯:說你行你就行不行也行
下聯:說不行就不行行也不行
橫批:不服不行
閱讀源碼就跟這個對聯一模一樣,就看你選上聯,還是下聯了!
這一篇開始源碼環境的搭建,here we go
!
很多老鐵留言說很想研讀些github上的開源項目,但代碼clone下來後總出現這樣或那樣奇奇怪怪的問題,很影響學習的積極性。學習ZooKeeper的源碼尤其如此,很多人clone代碼後,報各種錯,提示少各種包。問了下度娘ZooKeeper源碼環境,搜出來的文章真的差強人意,有些文章錯的竟然非常離譜。這裏我重新搭建了一遍,也會介紹遇到的一些坑。
很多老鐵上來一堆猛操作,從github上下載了ZooKeeper源碼後,按常規方式導入IDEA,最後發現少各種包。起初我也是這樣弄的,以爲ZooKeeper是用Maven來構建的,仔細去了解了下ZooKeeper的版本歷史,其實是用的Ant。如今一般用的Maven或Gradle,很少見到Ant的項目了,這裏不對Ant多做介紹。
1 Ant環境搭建
Ant官網地址:https://ant.apache.org/bindownload.cgi
下載解壓後,跟配置jdk一樣配置幾個環境變量:
//修改爲自己本地安裝的目錄
ANT_HOMT=D:\apache-ant-1.10.7
PATH=%ANT_HOME%/bin
CLASSPATH=%ANT_HOME%/lib
配置好後,測試下Ant是否安裝成功。ant -version,得到如下信息則代表安裝成功:
Apache Ant(TM) version 1.10.7 compiled on September 1 2019
Ant的安裝跟JDK的安裝和配置非常相似,這裏不做過多介紹。
2 下載ZooKeeper源碼
源碼地址:https://github.com/apache/zookeeper
猿人谷在寫本篇文章時,releases列表裏的最新版本爲release-3.5.6
,我們以此版本來進行源碼環境的搭建。
3 編譯ZooKeeper源碼
切換到源碼所在目錄,運行ant eclipse
將項目編譯並轉成eclipse的項目結構。
這個編譯過程會比較長,差不多等了7分鐘。如果編譯成功,會出現如下結果:
4 導入IDEA
上面已經將項目編譯並轉成eclipse的項目結構,按eclipse的形式導入項目。
5 特別說明
將源碼導入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";
}
6 啓動zookeeper
針對單機版本和集羣版本,分別對應兩個啓動類:
- 單機:ZooKeeperServerMain
- 集羣:QuorumPeerMain
這裏我們只做單機版的測試。
在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。
7 啓動客戶端
通過運行ZooKeeperServerMain
得到的日誌,可以得知ZooKeeper服務端已經啓動,服務的地址爲127.0.0.1:2181
。啓動客戶端來進行連接測試。
客戶端的啓動類爲org.apache.zookeeper.ZooKeeperMain
,進行如下配置:
即客戶端連接127.0.0.1:2181,獲取節點/yuanrengu
的信息。
下面帶領大家一起看看客戶端啓動的源碼(org.apache.zookeeper.ZooKeeperMain
)。這裏要給大家說下我閱讀源碼的習慣,很多老鐵以爲閱讀源碼就是順着代碼看,這樣也沒啥不對,只是很多開源項目代碼量驚人,這麼個幹看法,容易注意力分散也容易看花眼。我一般是基於某個功能點,從入口開始debug跑一遍,弄清這個功能的“代碼線”,就像跑馬圈塊地兒一樣,弄清楚功能有關的代碼,瞭解參數傳遞的過程,這樣看代碼時就更有針對性,也能排除很多幹擾代碼。
7.1 main
main裏就兩行代碼,通過debug得知args裏包含的信息就是上面我們配置在Program arguments裏的信息:
7.1.1 ZooKeeperMain
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。
7.1.2 parseOptions
可以很清楚的得知解析args的過程,主要從"-server","-timeout","-r","-"這幾個維度來進行解析。
7.1.3 connectToZK
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);的整個過程。簡短點說就是:
- 解析Program arguments裏的參數
- 連接ZooKeeper服務端
連接ZooKeeper的過程也是非常精彩的:
會進一步調用如下方法:
會多一個createDefaultHostProvider(connectString)
的步驟,其中connectString爲127.0.0.1:2181 。
繼續擼:
這裏多了一個ZKClientConfig
類型的參數,默認爲null。ZKClientConfig是在3.5.2版本中新加的,就是爲了在不同的ZooKeeper實例中實現更加靈活的屬性配置
。
ZooKeeper的連接過程是不是越來越精彩?那咱繼續擼:
其中createConnection的方法也是重點,被標註爲VisibleForTesting:
// @VisibleForTesting
protected ClientCnxn createConnection(String chrootPath,
HostProvider hostProvider, int sessionTimeout, ZooKeeper zooKeeper,
ClientWatchManager watcher, ClientCnxnSocket clientCnxnSocket,
boolean canBeReadOnly) throws IOException {
return new ClientCnxn(chrootPath, hostProvider, sessionTimeout, this,
watchManager, clientCnxnSocket, canBeReadOnly);
}
createConnection方法裏核心代碼如下:
其中有兩行代碼特別有意思,可以通過上面的圖來觀察參數中值的變化:
// 鏈接超時時間
connectTimeout = sessionTimeout / hostProvider.size();
// 讀取響應超時時間
readTimeout = sessionTimeout * 2 / 3;
隨着執行cnxn.start();至此ZooKeeper的連接就已完成:
7.2 main.run()
敲黑板,重頭戲來了哦!
一起來看下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
連接服務端。
在分析連接ZooKeeper的過程中,出現了HostProvider、ZKClientConfig、ClientCnxn等,這些都是非常重要的概念,系列後面的文章中會有一篇從源碼層面深入剖析連接ZooKeeper服務端的文章,重點進行講解爲什麼需要這類東西,以及它們到底能起到什麼妙用。
閱讀源碼最好能動起來(debug)讀,這樣代碼纔是活的,幹看的話代碼如死水一樣,容易讓人索然無味!
每個人操作的方式不一樣,有可能遇到的問題也不一樣,搭建過程中遇到什麼問題,大家可以在評論區留言。