Hadoop源碼學習筆記(6)——從ls命令一路解剖

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

  1. public static void main(String argv[]) throws Exception {
  2.    FsShell shell = new FsShell();
  3.    int res;
  4.    try {
  5.      res = ToolRunner.run(shell, argv);
  6.    } finally {
  7.      shell.close();
  8.    }
  9.    System.exit(res);
  10.  }

它這裏,經過ToolRunner繞了一下,目的是把argv進行了下處理,但最終回到了FsShell的run函數中,進之: spa

發現,裏面有大量的if else 條件都是cmd判斷是否等於某個命令,因而找到ls命令,發現其調了FsShell下的ls函數,進之:

  1. private int ls(String srcf, boolean recursive) throws IOException {
  2.     Path srcPath = new Path(srcf);
  3.     FileSystem srcFs = srcPath.getFileSystem(this.getConf());
  4.     FileStatus[] srcs = srcFs.globStatus(srcPath);
  5.     if (srcs==null || srcs.length==0) {
  6.       throw new FileNotFoundException("Cannot access " + srcf +
  7.           ": No such file or directory.");
  8.     }
  9.  
  10.     boolean printHeader = (srcs.length == 1) ? true: false;
  11.     int numOfErrors = 0;
  12.     for(int i=0; i<srcs.length; i++) {
  13.       numOfErrors += ls(srcs[i], srcFs, recursive, printHeader);
  14.     }
  15.     return numOfErrors == 0 ? 0 : -1;
  16.   }

這個ls函數行數很少,容易看清,參數是第一個是文件路徑,第二個是遞歸。

而後前2~4行,就能夠看到,獲取到了文件狀態,第13行,是打印顯示查到的文件狀態結果。這裏就不進入看了。

因而提到出2~4行代碼,加入到咱們的測試程序中。同時把以前的調用註釋掉:

  1. // 列出文件(原)
  2.  //FsShell.main(new String[]{"-ls"});
  3.  FileSystem srcFs = FileSystem.get(conf);
  4.  FileStatus[] items = srcFs.listStatus(new Path("hdfs://localhost:9000/user/zjf"));
  5.  for (int i = 0; i < items.length; i++)
  6.  System.out.println( items[i].getPath().toUri().getPath());

小步快走,運行之,發現文件夾是對的,但打印內容少了,沒錯,由於咱們沒從FileStatus中全打出來。

 

這裏能夠看到,ls命令的核心是FileSystem中的listStatus函數了。

繼續動刀。

 

觀察FileSystemlistStatus函數,發現是個虛函數,同時FileSystem也是個一個抽象類,說明具體的listStatus實現,必然還在其它類中。因而監視srcFs類,發現其類型爲DistributedFileSystem因而,把代碼轉換一下:

DistributedFileSystem srcFs = (DistributedFileSystem)FileSystem.get(conf);

運行程序,沒有變化。說明路走對了。

繼續,想拋棄這個FileSystem類呢,就行看看它如何在get方法中建立DistributedFileSystem這個類的,一級級查,發現,最終是在這裏建立:

  1. private static FileSystem createFileSystem(URI uri, Configuration conf  ) throws IOException {
  2.   Class<?> clazz = conf.getClass("fs." + uri.getScheme() + ".impl", null);
  3.   if (clazz == null) {
  4.     throw new IOException("No FileSystem for scheme: " + uri.getScheme());
  5.   }
  6.   FileSystem fs = (FileSystem)ReflectionUtils.newInstance(clazz, conf);
  7.   fs.initialize(uri, conf);
  8.   return fs;
  9. }

這裏應用了反射,能夠看出,做者仍是很牛的,這函數跟據傳入的uri,的scheme(就是地址的前綴),來根據配置,建立相應的實現類,是一個工廠模式。

這裏咱們傳入的urihdfs:// 因此查詢參數:fs.hadfs.impl。因而到core-default.xml中查:

  1. <property>
  2.   <name>fs.hdfs.impl</name>
  3.   <value>org.apache.hadoop.hdfs.DistributedFileSystem</value>
  4.   <description>The FileSystem for hdfs: uris.</description>
  5. </property>

發現,此處配置文件,正是咱們的DistributeFileSystem類。

而後看到,建立對象完後,就調用了initialize函數。因而咱們把程序進一步改造:

  1. DistributedFileSystem srcFs = new DistributedFileSystem();
  2. srcFs.initialize(uri, conf);
  3. FileStatus[] items = srcFs.listStatus(new Path(
  4. ......

運行程序,保持正確結果。

此時,能夠殺入DistributedFileSystem類的listStatus函數了。

  1. public FileStatus[] listStatus(Path p) throws IOException {
  2.     FileStatus[] infos = dfs.listPaths(getPathName(p));
  3.     if (infos == null) return null;
  4.     FileStatus[] stats = new FileStatus[infos.length];
  5.     for (int i = 0; i < infos.length; i++) {
  6.       stats[i] = makeQualified(infos[i]);
  7.     }
  8.     return stats;
  9.   }

進入後,發現主要由這個dfs對象訪問,因而尋找dfs對象的建立:

this.dfs = new DFSClient(namenode, conf, statistics);

因而繼續把咱們的程序改造:

  1. Statistics statistics = new Statistics(uri.getScheme());
  2.        InetSocketAddress namenode = NameNode.getAddress(uri.getAuthority());
  3.       DFSClient dfs = new DFSClient(namenode, conf, statistics);
  4.       FileStatus[] items = dfs.listPaths("/user/zjf");
  5.       for (int i = 0; i < items.length; i++)
  6.          System.out.println(items[i].getPath().toUri().getPath());

改造後,運行,結果與以前相同,說明路走對了。

繼續前進,進入listPath函數,發現又是調用了ClientProtocol類的getListing函數。而這個ClientProtocol接口,好象有點熟悉了,在以前講RPC調用時,這個是一個給客戶端使用的接口,而具體的實現,在服務器端。

因此拋開DFSClient類,咱們本身也能夠建立這個RPC接口,而後本身調用。改形成以下:

  1. InetSocketAddress nameNodeAddr = NameNode.getAddress(uri.getAuthority());
  2.    ClientProtocol namenode = (ClientProtocol)RPC.getProxy(ClientProtocol.class,
  3.            ClientProtocol.versionID, nameNodeAddr, UnixUserGroupInformation.login(conf, true), conf,
  4.            NetUtils.getSocketFactory(conf, ClientProtocol.class));
  5.    FileStatus[] items = namenode.getListing("/user/zjf");
  6.    for (int i = 0; i < items.length; i++)
  7.       System.out.println(items[i].getPath().toUri().getPath());

此時,咱們在Client這端已經走到了盡頭,RPC調用getListing接口,具體實現是在nameNode服務器端了。

即然RPC在客戶端調用的接口,具體是在服務器端實現。那麼,咱們若是直接建立服務器端的實現類,調用相應類的函數,不也能產出相同的結果麼。

因而直接使用NameNode來建立實例,來調用:

  1. UserGroupInformation.setCurrentUser(UnixUserGroupInformation.login(conf, true));
  2.       NameNode namenode = new NameNode(conf);
  3.       FileStatus[] items = namenode.getListing("/user/zjf");
  4.       for (int i = 0; i < items.length; i++)
  5.          System.out.println(items[i].getPath().toUri().getPath());

運行該程序時,須要把以前的NameNode程序中止掉,由於咱們再也不須要這個服務,而是直接call這個服務中的方法了。

到目前爲止,咱們已經經過ls這個命令,從client端一殺到了namenode端。而後分析其它幾個命令(delete,mkdir,getFileInfo)與ls相同。因此要想再深刻看這些命令的處理,得在namenode中進一步研究。

namenode咱們知道,主要存放的是文件目錄信息,那利用上述這些命令,就能夠進一步研究其目錄的存儲方式。這一塊將在下一章中爲進一步探討。

相關文章
相關標籤/搜索