Hadoop源碼學習——從"-ls /"開始認識FsShell(3)

閑話少敘,言歸正傳。這次,我們從"-ls /"命令入手,窺探一下hdfs。
hdfs模塊提供了一個org.apache.hadoop.fs.FsShell類用來支持用戶在終端的命令行操作。我們在使用終端時輸入的命令,最終都會在FsShell這個類中執行。
首先,我們給FsShell類傳遞參數“-ls /”:


參數設置.jpg

然后找到“-ls /”命令執行運行的入口——main()函數:

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);
  }

我們可以看到,首先,main()函數實例化一個FsShell對象,實例化FsShell對象的工作包括加載conf,初始化fs(FileSystem)和trash(Trash)兩個成員變量為null。
接著,交給ToolRunner類去執行,ToolRunner的run方法的主要作用是解析參數。這里有個重要的輔助類提一下:GenericOptionsParser,具體是這個類來解析傳入的參數。解析完參數后,ToolRunner 的run()方法又返回FsShell的run方法。注意:FsShell實現了Tool接口,然后實現了Tool接口的run方法
然后,我們看到FsShell的run方法。
FsShell的run方法首先檢查參數(在上一步的ToolRunner的run方法中解析得到)的格式是否正確。接著,初始化FsShell

protected void init() throws IOException {
    getConf().setQuietMode(true);
  }

這里的getConf().setQuietMode(true),我并不懂,暫不深究。我們繼續往下看。
接下來,就是具體的參數匹配工作了。例如,在“-ls /”這個例子中就會匹配到:

else if ("-ls".equals(cmd)) {
        if (i < argv.length) {
          exitCode = doall(cmd, argv, i);
        } else {
          exitCode = ls(Path.CUR_DIR, false);
        } 

如果給定了特定目錄久執行doall方法,否則ls當前目錄(Path.CUR_DIR)。因為這里我們給定了特定目錄“/”,所以我們跳轉到doall方法里去。
以ls命令為例,doall方法就是執行所有的ls命令后面的目錄或文件的ls操作。例如,-ls <path1> <path2> <path3>。doall會以for循環的形式依次執行ls()這個方法:

else if ("-ls".equals(cmd)) {
          exitCode = ls(argv[i], false);
        }

我們緊接著跳轉到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);

這是ls開頭重要的三個步驟:
第一步:根據路徑創建一個Path對象。創建過程會先檢查該路徑是否合法,然后將路徑解析成uri(scheme://authority/path)格式;
第二步:根據上一步創建的Path對象獲取FileSystem對象(具體獲取過程我們在下一篇文章中單獨講,因為這里面涉及DFSClient、DistributedFileSystem、NameNode,內容較多)。這個FileSystem對象決定了我們要讀取的文件是在哪個類型的文件系統中,例如hdfs或者本地文件系統。
第三步:在對應的文件系統中,獲取對應路徑下文件的屬性(FileStatus)。這一步更重要,過程涉及DFSClient與NamNode之間的通信,即Hadoop ipc相關內容,也需要單獨講。
注意,在第三步中的srcPath這個參數,srcPath可以是一個具體的文件名字,也可以是文件名字的匹配模式,例如"/t",這種模式就可以匹配到/test和/tmp這兩個文件,srcs.length也就是2了,而不是我之前以為的好像只能是1。*如果srcs.length>1,則不會打印此次ls的信息頭(即發現文件的個數,“Found xxx items”)
在這里,我們先簡單地說這三步做了什么。

在獲取到FileStatus[] srcs后(假設我們這里獲取到了srcs[2]={"/test","/tmp"}兩個目錄),就循環遍歷srcs,執行ls:

for(int i=0; i<srcs.length; i++) {
      numOfErrors += ls(srcs[i], srcFs, recursive, printHeader);
    }

接著跳轉到又一個ls方法(假設第一次循環傳過來的參數是"/test")中:

private int ls(FileStatus src, FileSystem srcFs, boolean recursive,
      boolean printHeader) throws IOException {
    final String cmd = recursive? "lsr": "ls";
    final FileStatus[] items = shellListStatus(cmd, srcFs, src);

剛跳過來,又要跳到shellListStatus方法中去了:

private static FileStatus[] shellListStatus(String cmd, 
                                                   FileSystem srcFs,
                                                   FileStatus src) {
    if (!src.isDir()) {
      FileStatus[] files = { src };
      return files;
    }
    Path path = src.getPath();
    try {
      FileStatus[] files = srcFs.listStatus(path);

shellListStatus方法會首先判斷路徑src(/test)是目錄還是文件,如果是文件則直接返回,如果是目錄,則要調用listStatus方法獲取該目錄所有文件的status。
DistributedFileSystem類實現了FileSystem里listStatus()這個抽象方法。DistributedFileSystem類的list Status按兩步獲取一個目錄下的所有文件。先取一部分數量的文件,如果沒有其它的文件了,則獲取結束。如果還有其它的文件,則循環獲取該目錄下的文件,直到獲取完所有的文件。
然后,跳轉到最近的一個ls方法中,繼續執行。現在我們已經獲取了一個目錄下的所有文件信息(例如"/test/a","/test/b")。接下來就是打印所有文件的信息:

// 接著上面最近的ls方法
int maxReplication = 3, maxLen = 10, maxOwner = 0,maxGroup = 0;
      System.out.println("FsShell's ls's items.length: " + items.length);
      for(int i = 0; i < items.length; i++) {
        FileStatus stat = items[i];
        int replication = String.valueOf(stat.getReplication()).length();
        int len = String.valueOf(stat.getLen()).length();
        int owner = String.valueOf(stat.getOwner()).length();
        int group = String.valueOf(stat.getGroup()).length();

        if (replication > maxReplication) maxReplication = replication;
        if (len > maxLen) maxLen = len;
        if (owner > maxOwner)  maxOwner = owner;
        if (group > maxGroup)  maxGroup = group; /
                                                          }

一開始,我看不懂這段代碼的意思,后來終于看明白了。這段代碼用來/獲取文件各個屬性的最大字符寬度,以便規整文件信息打印的格式。舉個例子,文件/test/a的owner是wuyi,文件/test/b的owner是wy, 那么,為了打印的時候格式規整,則/test/b的owner打印是也給定4個字符的寬度。

for (int i = 0; i < items.length; i++) {
        FileStatus stat = items[i];
        Path cur = stat.getPath();
        String mdate = dateForm.format(new Date(stat.getModificationTime()));
        System.out.print((stat.isDir() ? "d" : "-") + 
          stat.getPermission() + " ");
        System.out.printf("%"+ maxReplication + 
          "s ", (!stat.isDir() ? stat.getReplication() : "-"));
        if (maxOwner > 0)
          System.out.printf("%-"+ maxOwner + "s ", stat.getOwner());
        if (maxGroup > 0)
          System.out.printf("%-"+ maxGroup + "s ", stat.getGroup());
        System.out.printf("%"+ maxLen + "d ", stat.getLen());
        System.out.print(mdate + " ");
        System.out.println(cur.toUri().getPath());
        if (recursive && stat.isDir()) {
          numOfErrors += ls(stat,srcFs, recursive, printHeader);
        }
      }

接下來這段代碼,就可以和我們在控制臺看到的ls的輸出結果可以對應起來了。例如:

-rw-r--r--   1 wuyi supergroup         92 2017-05-26 08:45 /test/a
-rw-r--r--   1 wuyi supergroup         63 2017-05-26 08:47 /test/b

至此,ls的整改流程也就通了。確切地說,應該是FsShell這段的流程。因為,我們知道,文件真正存儲的地方是hdfs,所以想要獲取文件信息,必定要訪問namenode,這就涉及到hadoop ipc通信問題了。這是我們接下來需要進一步探討的。

總結一下##

FsShell其實就是我們平常用終端寫命令時,最終的程序運行入口。所以,FsShell命令還包括了我們常見的很多命令,例如put(舊版本的copyFromLocal)、get(舊版本的copyToLocal)、mkdir、mv、cp、rm、cat、chmod、chown、chgrp等等。而在FsShell類里所做的主要工作就是對這些命令的解析,即這些命令體現在文件系統中具體行為。當然,這其中也需要文件系統的配合(例如DistributedFileSystem中的listStatus方法)。我們接下來要重點關注的是(Distributed)FileSystem和DFSClient和Namenode三者的協作關系以及工作原理。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • linux資料總章2.1 1.0寫的不好抱歉 但是2.0已經改了很多 但是錯誤還是無法避免 以后資料會慢慢更新 大...
    數據革命閱讀 12,218評論 2 33
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,923評論 18 139
  • Ubuntu的發音 Ubuntu,源于非洲祖魯人和科薩人的語言,發作 oo-boon-too 的音。了解發音是有意...
    螢火蟲de夢閱讀 99,560評論 9 467
  • 當數據量增大到超出了單個物理計算機存儲容量時,有必要把它分開存儲在多個不同的計算機中。那些管理存儲在多個網絡互連的...
    單行線的旋律閱讀 1,954評論 0 7
  • 兒子:“爸,以后碰到事情,不要自作主張,先充分尊重我和我媽的意見。” 老公:“嗯,記住了!” 兒子:“一句話,把我...
    銀子姐閱讀 237評論 4 3