Hadoop學習筆記(9)
——源碼初窺
之前我們把Hadoop算是入了門,下載的源碼,寫了HelloWorld,簡要分析了其編程要點,然后也編了個較復雜的示例。接下來其實就有兩條路可走了,一條是繼續深入研究其編程及部署等,讓其功能使用的淋漓盡致。二是停下來,先看看其源碼,研究下如何實現的。在這里我就選擇第二條路。
研究源碼,那我們就來先看一下整個目錄里有點啥:

這個是剛下完代碼后,目錄列表中的內容。
| 目錄/文件 |
說明 |
| bin |
下面存放着可執行的sh命名,所有操作都在這里 |
| conf |
配置文件所在目錄 |
| ivy |
Apache Ivy是專門用來管理項目的jar包依賴的,這個是ivy的主要目錄 |
| lib |
引用的庫文件目錄,里面存放用到的jar包 |
| src |
這個里面就是主要的源碼了 |
| build.xml |
用於編譯的配置文件。 編譯我們用的是ant |
| CHANGES.txt |
文本文件,記錄着本版本的變更歷史 |
| ivy.xml |
Ivy的配置文件 |
| LICENSE.txt |
文件本文件, |
| NOTICE.txt |
文本文件,記錄着需要注意的地方 |
| README.txt |
說明文件。 |
進入src目錄,我們看到了:

| 目錄/文件 |
說明 |
| ant |
為ant命令編寫的擴展指定 |
| benchmarks |
筆者也沒弄明白L |
| build |
就存放一個打包信息文件 |
| c++ |
linux下amd64-64位系統以及i386-32位系統提供的庫文件集合 |
| contrib |
是開源界或第三方為hadoop編寫的一些擴展程序,如eclipse插件等 |
| core |
Hadoop的核心代碼 |
| docs |
文檔 |
| examples |
示例程序 |
| hdfs |
HDFS模塊的代碼 |
| marped |
MapReduce模塊代碼 |
| native |
筆者也沒弄明白L |
| test |
測試程序 |
| tools |
工具集 |
| webapps |
網頁管理工具的代碼,主要是jsp文件。 |
| fixFontsPath.sh |
用於修正字體路徑的批處理命令。 |
| saveVersion.sh |
用於生成打包信息文件批處理命令。 |
這些目錄及文件命名及分布還是很清晰的,基本上根據命名也能猜出其意思來了。當我們拿到這些文件時,做了兩件事,編譯和運行,接下來我們一塊塊仔細來看看。
編譯
當我們拿到手時,第一章中講到,我們用了以下命令就完成了編譯:
~/hadoop-0.20.2$ant
~/hadoop-0.20.2$ant jar
~/hadoop-0.20.2$ant examples
在編譯完后,我們發現,目錄中多了一個build文件夾。這個文件夾下,我們發現有大量的子文件夾,再深入看,可以找到了N多個.class文件。那這個正是java程序的編譯產出物。
我們在第5章中,簡要的描述了java程序與.net的差別。一個.java程序對應一個.class文件,手動的話用javac來編譯。我們要將這么多的java文件都要編譯成一個個的.class文件,敲javac命令肯定是不行的,我們得找個打包處理的辦法。這個就是ant。簡單的說ant就是將編譯命名進行打包處理的程序,這個程序有一個配置文件就是build.xml。所以我們進入hadoop根目錄后輸入了ant后就開始運行了,因為它在當前目錄下找到了build.xml文件。那ant能做啥,其實百度上一搜就有很多了。這里就不詳述了。我們簡要的來看一下build.xml。 打開一看,build.xml文件貌似很復雜,有1千8百多行。不要怕,簡單看下:

一上來,定義了一個project,看來這是一個工程,有名稱和default屬性(default后面看是啥)。
接下來發現是一堆的property,然后是name-value的健值。應該猜的出,這些就是后面真正執行用的一些變量或參數。
再往下,看到有這些:

看到有target,然后取了個名,字面意思是目標,然后看看子結點,發現是mkdir,好熟悉的字眼,這不是在創建目錄么,看下第一個dir是啥,${build.dir}。然后立即跑回上面property中,看下是否有呢?

果然,這個就是在編譯后的產生的目錄,第一步創建之,很正常。
既然這樣,這個target就是一個個目標,然后往下拖一下,發現下面的都是一個個的目錄,全文搜索一下:

發現里面有106個。
繼續搜,發現了亮點:

這個target(目標)好眼熟,~/hadoop-0.20.2$ant jar 沒錯,當時在編譯時,輸入這個命令后,就產出了一個jar文件。看來這個target就是在形成jar文件,略看下其子命令,的確就是在生成jar包了。
簡單了解了這個target后,就可以繼續找找,我們的examples命令了。現回想起來,在編譯時第一個命令是~/hadoop-0.20.2$ant,而這個好象沒有寫target么?又想到了:

難道這個default就是傳說中的默認目標? compile。 熬不住了,立即展開搜索:

果然,猜的沒錯。找到了這個默認目錄,然后發現好多target后還有depends,字面意思,依賴吧,然后可以繼續找,依賴里面的目錄,也是一個個的target。
了解了這個之后,我們又在想,現在知道的target也就 默認、jar、example,還有哪些呢,我們就可以搜target name="這個字符。當然會發現有很多,但是不是每個都對我們有用,因為好多是為了編寫方便,將一個大的拆成多個小的,以便於維護。至於哪些有用的,這里我就不一一列出。可以自己看看。 比如clean就不錯,可以把編譯后的結果清理掉,還原到開始狀態。
編譯成.class包括jar包現在都沒問題了。我們知道hadoop是用java寫的,在src下可找到大量java類文件。難道這個hadoop就沒有引用一個第三方的組件?答案是有的,一開始沒看到幾個,在lib下就只有幾個。 但是在ant完后,在build下搜,發現有好多個jar文件。 哪來的? 下載的。誰負責下載的,為什么知道要下載這些文件?
我們發現,在build.xml中,第一個target init就有depends:

然后就可以一級級查到,是通用ivy進行下載的,至於下載哪些,在ivy.xml中就有配置。好了,這塊並不是我們的重點,了解到這里就夠了,反正所用到的lib文件都下來了。
運行
在第一章中,我們了解到啟用整個hadoop,全到了這個命令:bin/start-all.sh,關閉是用到了bin/stop-all.sh。而這個又是什么文件,我們來研究一下看。
不急看start-all, 我們打開bin目錄看一下:

在bin下有很多個sh文件,hadoop這個命令,雖然沒有后綴,但打開看后,發現跟其他sh文件樣,類似的腳本。
什么是sh文件? 在windows中我們知道bat文件,就是將若干個命令放到一個文件中,依次執行,稱之為批處理文件。在Linux中,這個bat文件就是sh文件了。
先不急着打開文件內容,我們觀察下所以文件,看到下面8個,很有規律,4個startXXX.sh然后4個stopXXX.sh文件。看來這些就是用戶啟動和關閉hadoop用的。
打開start-all.sh,發現內容並不多,也很好理解:

這里,先調了一下hadoop-config.sh,字面意思,設置配置文件。然后再調了start-dfs 和start-mapred。這里就很明顯了,start-all是啟動整個hadoop,然后里面包含了兩個動作,啟動dfs和mapreduce。 同理,如果我想只啟動dfs,那么只需要運行start-dfs.sh即可。
同樣,打開stop-all.sh文件,也可以看到比較簡單,

發現是分別調了stop-mapred.sh和stop-dfs.sh這兩個文件。
這里我們就不每個文件進行分析了,我們只挑幾個關鍵文件看一下。
繼續前行,打開start-dfs.sh和stop-dfs.sh文件,發現里面

和

大家可以打開其他所有的startXX和stopXX文件,發現所有的操作都又轉入了hadoop-daemon.sh和hadoop-daemons.sh這兩個命令,同時傳入了參數—config stop/start 名稱 (opt參數)。
繼續,打開hadoop-daemons.sh,發現內容也很簡單:

這里,先調用了slaves.sh后,又調回來hadoop-daemon.sh,所以現在目標焦點就只有兩個了hadoop-daemon.sh和slaves.sh了。打開slaves.sh看一下:

這個文件的字面意思應該就是啟動各分布式子機的hadoop咯。看一下代碼,第一個if與fi之間,可以看到是取得conf文件夾下的slaves文件。記得在配置分配布式里面,在slaves中配置寫了是node1 node2用回車換行隔開。 所以第二段代碼,for循環slaves中的文件,然后調用ssh命令,調到了子系統中的相應的命令,這里,就完全可以想通了,為什么子系統中部署的hadoop目錄需要與主目錄相同,然后slaves中配置的是子系統機器的名稱。
到這里,整個bin目錄的腳本,就集中在剩下的兩個hadoop-daemon.sh和hadoop了。勝利在望了。先看hadoop-daemon.sh。

一開始,代碼是在取參數,startstop和command,從前面的傳入可以看到,startstop參數傳的是start和stop,看來是啟動和關閉, command是namenode、datanode之類的。
繼續往下看:

case語句下進行了分類,將start和stop命令分開處理。在start時,先是創建一個文件夾PID_DIR,具體值可以看上面,然后第一段if,是在判斷進程有沒有啟動,然后最關健是執行nohup nice …. /bin/hadoop。也就是說歸根到底又都是在執行hadoop命令了。這里nohup,是指啟動進程后不被卡住,即轉為后台進程,又稱守護進程,所以該sh文件命名為daemon也不為過。
然后stop段時,把進程進行kill掉。這里有疑問了,啟動的命令kill里需要知道進程的PID,而kill里哪里獲取呢,在啟動時,將啟動進程后的pid記錄在PID文件夾內,然后kill時就可以跟據這些PID來處理了。這塊在代碼中,也比較清晰的體現了。
在執行hadoop命令時,又將namenode、datanode、secondarynamenode等命令傳入。所以現在可以打開hadoop命令文件了:(這里直接跳入重點看)

這里,看到有大量的if語句,條件是command判斷,然后執行中對class和hadoop_opts進行了賦值。 繼續往下看:(在最后)

我們發現,是在執行java命令,傳入的main函數入口正是上面條件處理中的CLASS變量。換句話說,這個CLASS應該對應一個個的main函數咯? 驗證一下,找一個,比如dataNode,其CLASS是org.apache.hadoop.hdfs.server.datanode.DataNode。按這路徑在src中找到文件DataNode.java,打開,然后搜main:

果然,完全應正了我們的想法。
總結一下:整個hadoop程序,是一個java為主的程序,在編譯是將.class文件生成在build目錄,在運行時,雖然執行的是.sh文件,但一步步,最終都是在執行java命令,傳入的入口,就是各個子程序的main函數入口。
想法1:看了這個sh命令后,又有一個想法,之前通過starg-all.sh就把整個程序啟動起來了,而且是在后台運行的,輸出內容只能從log文件夾內看,能否直接從命令行啟動呢? 當然行,輸入 bin/hadoop namenode試試,果然,啟動了namenode程序,然后日志信息也直接打印在屏幕上了。
想法2:既然從hadoop這個sh文件夾內,可以看到所有的入口,那就可以整理一下,所有的入口成一個列表,方便以后找到其main函數。
| 命令 |
入口 |
| namenode |
org.apache.hadoop.hdfs.server.namenode.NameNode |
| secondarynamenode |
org.apache.hadoop.hdfs.server.namenode.SecondaryNameNode |
| datanode |
org.apache.hadoop.hdfs.server.datanode.DataNode |
| fs / dfs |
org.apache.hadoop.fs.FsShell |
| dfsadmin |
org.apache.hadoop.hdfs.tools.DFSAdmin |
| mradmin |
org.apache.hadoop.mapred.tools.MRAdmin |
| fsck |
org.apache.hadoop.hdfs.tools.DFSck |
| balancer |
org.apache.hadoop.hdfs.server.balancer.Balancer |
| jobtracker |
org.apache.hadoop.mapred.JobTracker |
| tasktracker |
org.apache.hadoop.mapred.TaskTracker |
| job |
org.apache.hadoop.mapred.JobClient |
| queue |
org.apache.hadoop.mapred.JobQueueClient |
| pipes |
org.apache.hadoop.mapred.pipes.Submitter |
| version |
org.apache.hadoop.util.VersionInfo |
| jar |
org.apache.hadoop.util.RunJar |
| distcp |
org.apache.hadoop.tools.DistCp |
| daemonlog |
org.apache.hadoop.log.LogLevel |
| archive |
org.apache.hadoop.tools.HadoopArchives |
| sampler |
org.apache.hadoop.mapred.lib.InputSampler |
至此整個目錄有了一個初步的了解,接下來,那就可以順着這些入口深入研究了。且慢,還差個調試環境呢! 下一章來。
