Hadoop源碼學習筆記(6) node
——從ls命令一路解剖 shell
Hadoop幾個模塊的程序咱們大體有了點了解,如今咱們得細看一下這個程序是如何處理命令的。 咱們就從原頭開始,而後一步步追查。 apache
咱們先選中ls命令,這是一個列出分面式文件系統中的目錄結構。傳入一個查閱地址,若是沒有則是根目錄。啓動NameNode和DataNode服務。而後在命令行中輸入ls : 服務器
換成程序,若是寫呢,咱們新建一個ClientEnter類。以前章節中,咱們就知道,在命令行中輸入的dfs命令,指向到org.apache.hadoop.fs.FsShell,則這個類入口是一個main函數。因此先直接用,在clientEnter類的main函數中加入下面代碼: 函數
FsShell.main(new String[]{"-ls"}); oop
而後執行,看到下面結果: 學習
這個結果,與控制檯中的是同樣的,因此說調用正確。注意,這裏多加了幾行初使化用的,先寫上,後面會用。 測試
動刀解剖,咱們進入FsShell的main函數中,看看裏面是怎麼實現的: this
-
public static void main(String argv[]) throws Exception {
-
FsShell shell = new FsShell();
-
int res;
-
try {
-
res = ToolRunner.run(shell, argv);
-
} finally {
-
shell.close();
-
}
-
System.exit(res);
-
}
它這裏,經過ToolRunner繞了一下,目的是把argv進行了下處理,但最終回到了FsShell的run函數中,進之: spa
發現,裏面有大量的if else 條件都是cmd判斷是否等於某個命令,因而找到ls命令,發現其調了FsShell下的ls函數,進之:
-
private int ls(String srcf, boolean recursive) throws IOException {
-
Path srcPath = new Path(srcf);
-
FileSystem srcFs = srcPath.getFileSystem(this.getConf());
-
FileStatus[] srcs = srcFs.globStatus(srcPath);
-
if (srcs==null || srcs.length==0) {
-
throw new FileNotFoundException("Cannot access " + srcf +
-
": No such file or directory.");
-
}
-
-
boolean printHeader = (srcs.length == 1) ? true: false;
-
int numOfErrors = 0;
-
for(int i=0; i<srcs.length; i++) {
-
numOfErrors += ls(srcs[i], srcFs, recursive, printHeader);
-
}
-
return numOfErrors == 0 ? 0 : -1;
-
}
這個ls函數行數很少,容易看清,參數是第一個是文件路徑,第二個是遞歸。
而後前2~4行,就能夠看到,獲取到了文件狀態,第13行,是打印顯示查到的文件狀態結果。這裏就不進入看了。
因而提到出2~4行代碼,加入到咱們的測試程序中。同時把以前的調用註釋掉:
-
// 列出文件(原)
-
//FsShell.main(new String[]{"-ls"});
-
FileSystem srcFs = FileSystem.get(conf);
-
FileStatus[] items = srcFs.listStatus(new Path("hdfs://localhost:9000/user/zjf"));
-
for (int i = 0; i < items.length; i++)
-
System.out.println( items[i].getPath().toUri().getPath());
小步快走,運行之,發現文件夾是對的,但打印內容少了,沒錯,由於咱們沒從FileStatus中全打出來。
這裏能夠看到,ls命令的核心是FileSystem中的listStatus函數了。
繼續動刀。
觀察FileSystem的listStatus函數,發現是個虛函數,同時FileSystem也是個一個抽象類,說明具體的listStatus實現,必然還在其它類中。因而監視srcFs類,發現其類型爲DistributedFileSystem因而,把代碼轉換一下:
DistributedFileSystem srcFs = (DistributedFileSystem)FileSystem.get(conf);
運行程序,沒有變化。說明路走對了。
繼續,想拋棄這個FileSystem類呢,就行看看它如何在get方法中建立DistributedFileSystem這個類的,一級級查,發現,最終是在這裏建立:
-
private static FileSystem createFileSystem(URI uri, Configuration conf ) throws IOException {
-
Class<?> clazz = conf.getClass("fs." + uri.getScheme() + ".impl", null);
-
if (clazz == null) {
-
throw new IOException("No FileSystem for scheme: " + uri.getScheme());
-
}
-
FileSystem fs = (FileSystem)ReflectionUtils.newInstance(clazz, conf);
-
fs.initialize(uri, conf);
-
return fs;
-
}
這裏應用了反射,能夠看出,做者仍是很牛的,這函數跟據傳入的uri,的scheme(就是地址的前綴),來根據配置,建立相應的實現類,是一個工廠模式。
這裏咱們傳入的uri是hdfs:// 因此查詢參數:fs.hadfs.impl。因而到core-default.xml中查:
-
<property>
-
<name>fs.hdfs.impl</name>
-
<value>org.apache.hadoop.hdfs.DistributedFileSystem</value>
-
<description>The FileSystem for hdfs: uris.</description>
-
</property>
發現,此處配置文件,正是咱們的DistributeFileSystem類。
而後看到,建立對象完後,就調用了initialize函數。因而咱們把程序進一步改造:
-
DistributedFileSystem srcFs = new DistributedFileSystem();
-
srcFs.initialize(uri, conf);
-
FileStatus[] items = srcFs.listStatus(new Path(
-
......
運行程序,保持正確結果。
此時,能夠殺入DistributedFileSystem類的listStatus函數了。
-
public FileStatus[] listStatus(Path p) throws IOException {
-
FileStatus[] infos = dfs.listPaths(getPathName(p));
-
if (infos == null) return null;
-
FileStatus[] stats = new FileStatus[infos.length];
-
for (int i = 0; i < infos.length; i++) {
-
stats[i] = makeQualified(infos[i]);
-
}
-
return stats;
-
}
進入後,發現主要由這個dfs對象訪問,因而尋找dfs對象的建立:
this.dfs = new DFSClient(namenode, conf, statistics);
因而繼續把咱們的程序改造:
-
Statistics statistics = new Statistics(uri.getScheme());
-
InetSocketAddress namenode = NameNode.getAddress(uri.getAuthority());
-
DFSClient dfs = new DFSClient(namenode, conf, statistics);
-
FileStatus[] items = dfs.listPaths("/user/zjf");
-
for (int i = 0; i < items.length; i++)
-
System.out.println(items[i].getPath().toUri().getPath());
改造後,運行,結果與以前相同,說明路走對了。
繼續前進,進入listPath函數,發現又是調用了ClientProtocol類的getListing函數。而這個ClientProtocol接口,好象有點熟悉了,在以前講RPC調用時,這個是一個給客戶端使用的接口,而具體的實現,在服務器端。
因此拋開DFSClient類,咱們本身也能夠建立這個RPC接口,而後本身調用。改形成以下:
-
InetSocketAddress nameNodeAddr = NameNode.getAddress(uri.getAuthority());
-
ClientProtocol namenode = (ClientProtocol)RPC.getProxy(ClientProtocol.class,
-
ClientProtocol.versionID, nameNodeAddr, UnixUserGroupInformation.login(conf, true), conf,
-
NetUtils.getSocketFactory(conf, ClientProtocol.class));
-
FileStatus[] items = namenode.getListing("/user/zjf");
-
for (int i = 0; i < items.length; i++)
-
System.out.println(items[i].getPath().toUri().getPath());
此時,咱們在Client這端已經走到了盡頭,RPC調用getListing接口,具體實現是在nameNode服務器端了。
即然RPC在客戶端調用的接口,具體是在服務器端實現。那麼,咱們若是直接建立服務器端的實現類,調用相應類的函數,不也能產出相同的結果麼。
因而直接使用NameNode來建立實例,來調用:
-
UserGroupInformation.setCurrentUser(UnixUserGroupInformation.login(conf, true));
-
NameNode namenode = new NameNode(conf);
-
FileStatus[] items = namenode.getListing("/user/zjf");
-
for (int i = 0; i < items.length; i++)
-
System.out.println(items[i].getPath().toUri().getPath());
運行該程序時,須要把以前的NameNode程序中止掉,由於咱們再也不須要這個服務,而是直接call這個服務中的方法了。
到目前爲止,咱們已經經過ls這個命令,從client端一殺到了namenode端。而後分析其它幾個命令(delete,mkdir,getFileInfo)與ls相同。因此要想再深刻看這些命令的處理,得在namenode中進一步研究。
namenode咱們知道,主要存放的是文件目錄信息,那利用上述這些命令,就能夠進一步研究其目錄的存儲方式。這一塊將在下一章中爲進一步探討。