Hadoop-5-HDFS

Hadoop-5-HDFS

1、概述

 Hadoop Distributed File System (HDFS™),是一種分佈式文件管理系統,用於管理多個機器上的文件

 它主要用於存儲文件,通過目錄樹結構來定位文件的位置。適合一次寫入,多次讀出的場景,不支持文件的修改,所以用它做數據的分析,而不是當作網盤

2、優缺點

【1】優點

  1. 高容錯性。容錯性體現在數據保存到多個副本上(DataNode),並且可以通過增加副本的數量(replication),來提高這種特性。並且當一個副本的數據丟失後,它還可以自動恢復
  2. 大數據量。一是處理數據的量級,可以達到TB、PB的級別。二是文件的數量,能夠處理百萬以上的規模
  3. 流式數據。一次寫入,多次寫出。只能追加,不能修改。保證數據的一致性
  4. 高可用性。可以將數據節點部署到多個廉價機器上

【2】缺點

  1. 不能實現低延遲的數據訪問。無法做到毫秒級的海量數據存儲
  2. 無法高效地存儲多個小文件。處理大量的小文件的存儲、目錄結構以及塊信息,會佔用NameNode的大量內存資源。此外小文件存儲時的尋址時間會超過其讀取時間,這違反了HDFS設計的初衷
  3. 不能併發寫入和隨機修改。對於一個文件,在同一時間內只能有一個寫入,不允許多個線程同時寫入。僅支持文件的追加,不支持文件的修改

3、架構設計

 主要由NameNode,DataNode和SecondaryNameNode組成

 需要注意的是SecondaryNameNode不是NameNode的熱備,所以當NameNode節點掛掉時,其並不能立即替換NameNode並提供服務。SecondaryNameNode是一個用於監控HDFS狀態的後臺輔助程序,它每隔一段時間就會獲取NameNode(HDFS元數據)的快照

4、塊

 HDFS中的文件在物理上是分塊存儲(block),塊的大小可以通過etc/hadoop/hdfs-site.xml中的dfs.blocksize來設置。apache hadoop 2.9.2默認大小爲134217728(128MB)

 HDFS的塊的大小比物理磁盤的塊的大小要大,這是爲了減少尋址的開銷。如果塊設置得足夠大,那麼數據傳輸的時間要明顯地久於塊尋址的時間

 尋址時間爲傳輸時間的1%時,是最佳狀態。計算公式:文件傳輸時間 = 磁盤尋址時間 / 0.01塊的大小 = 文件傳輸時間 * 磁盤傳輸速率

 根據公式:假如尋址時間是10s,傳輸速率是100MB/s,那麼塊的大小 = 10s / 0.01 * 100MB/s = 100MB

5、命令操作【常用命令】

 所有命令都是通過bin/hadoop fs 命令的方式執行的。注意命令前面都有一個橫槓

【1】創建目錄

-mkdir -p,選項p表示遞歸,和Linux命令一樣,不帶參數僅創建一級目錄

【2】查看目錄

-ls -R,選項R表示遞歸,它會一直到目錄的最裏面

【3】刪除目錄

-rmdir

【4】上傳文件

-copyFromLocal /本地文件 /HDFS目錄,複製本地文件到HDFS

-put /本地文件 /HDFS目錄,和copyFromLocal命令一樣

-moveFromLocal /本地文件 /HDFS目錄,移動本地文件到HDFS

【5】下載文件

-copyToLocal /HDFS文件 /本地目錄,複製HDFS文件到本地

-get /HDFS文件 /本地目錄,和copyToLocal命令一樣

-moveToLocal /HDFS文件 /本地目錄,移動HDFS文件到本地。

Hadoop 2.9.2,提示:moveToLocal: Option ‘-moveToLocal’ is not implemented yet.

【6】追加文件

-appendToFile /本地文件 /HDFS文件,將本地文件的內容追加到HDFS文件的末端

【7】查看文件

-cat /HDFS文件或目錄,查看文件,可以使用*通配符,和Linux命令一樣

-tail -f /HDFS文件,查看文件末尾,後41行,可以追加f選項,和Linux命令一樣

【8】刪除文件

-rm -r /HDFS目錄或文件,刪除文件或目錄,和Linux命令一樣

6、Java客戶端操作

【1】準備環境

  1. Java
  2. Maven
  3. 和服務端一致的Hadoop,並解壓縮到無中文目錄
  4. 下載winutils,並將hadoop-2.8.3/bin目錄裏的winutils.exe複製到解壓縮後的hadoop的bin目錄當中
  5. 配置環境變量:HADOOP_HOME到解壓縮目錄
  6. 打開IDE。一定要先配置好環境變量再打開IDE

【2】MAVEN依賴

<properties>
    <!-- 和服務端版本一致 -->
    <hadoop.version>2.9.2</hadoop.version>
</properties>

<dependency>
    <!-- hadoop-client間接依賴hadoop-common -->
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-client</artifactId>
    <version>${hadoop.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-hdfs</artifactId>
    <version>${hadoop.version}</version>
</dependency>

【3】獲取文件系統

通過org.apache.hadoop.fs.FileSystem對象的靜態方法來獲取

  1. FileSystem get(Configuration conf) throws IOException,需要傳遞一個org.apache.hadoop.conf.Configuration對象,該配置對象的屬性,可以從etc/hadoop/core-site.xml中獲取

    Configuration conf = new Configuration();
    // NameNode服務器的IP,9000是默認端口
    conf.set("fs.defaultFS", "hdfs://???.???.???.???:9000");
    FileSystem fileSystem = null;
    try {
        fileSystem = FileSystem.get(conf);
        System.out.println(fileSystem);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (null != fileSystem) {
            try {
                fileSystem.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
  2. FileSystem get(final URI uri, final Configuration conf, String user) throws IOException, InterruptedException

    URI uri = null;
    try {
        // NameNode服務器的IP,9000是默認端口
        uri = new URI("hdfs://???.???.???.???:9000");
    } catch (URISyntaxException e) {
        e.printStackTrace();
        return;
    }
    
    Configuration conf = new Configuration();
    FileSystem fileSystem = null;
    try {
        // 操作文件系統的用戶名
        fileSystem = FileSystem.get(uri, conf, "???");
        System.out.println(fileSystem);
    } catch (IOException | InterruptedException e) {
        e.printStackTrace();
    } finally {
        if (null != fileSystem) {
            try {
                fileSystem.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
  3. 【推薦】FileSystem get(URI uri, Configuration conf) throws IOException

    URI uri = null;
    try {
        // NameNode服務器的IP,9000是默認端口
        uri = new URI("hdfs://???.???.???.???:9000");
    } catch (URISyntaxException e) {
        e.printStackTrace();
        return;
    }
    
    // 操作文件系統的用戶名。這句話也可以配置到vm參數當中-DHADOOP_USER_NAME=???
    System.setProperty("HADOOP_USER_NAME", "???");
    
    Configuration conf = new Configuration();
    FileSystem fileSystem = null;
    try {
        fileSystem = FileSystem.get(uri, conf);
        System.out.println(fileSystem);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (null != fileSystem) {
            try {
                fileSystem.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

【4】參數配置的優先級

 通過org.apache.hadoop.conf.Configuration對象配置參數的優先級是最高的。接着是classpath路徑下的配置文件,如core-site.xml等。最後纔是默認配置的參數

【5】創建目錄

try {
    boolean mkdirs = fileSystem.mkdirs(new Path("/?/?/?"));
    System.out.println(mkdirs ? "成功" : "失敗");
} catch (IOException e) {
    e.printStackTrace();
}

【6】查看目錄

try {
    RemoteIterator<LocatedFileStatus> remoteIterator = fileSystem.listFiles(new Path("/"), true); // 第二個參數表示是否遞歸

    System.out.println("Permission\tOwner\tGroup\t\tSize\tLast Modified\tPath\t\tReplication");
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM-dd HH:mm");

    while (remoteIterator.hasNext()) {
        LocatedFileStatus locatedFileStatus = remoteIterator.next();

        String permission = locatedFileStatus.getPermission().toString();
        StringBuilder line = new StringBuilder(permission).append("\t");

        String owner = locatedFileStatus.getOwner();
        line.append(owner).append("\t");

        String group = locatedFileStatus.getGroup();
        line.append(group).append("\t");

        long size = locatedFileStatus.getLen();
        line.append(size).append("\t");

        long lastModified = locatedFileStatus.getModificationTime();
        String time = simpleDateFormat.format(new Date(lastModified));
        line.append(time).append("\t\t");

        Path path = locatedFileStatus.getPath();
        String pathStr = path.toString();
        line.append(pathStr).append("\t\t");

        short replication = locatedFileStatus.getReplication();
        line.append(replication);

        BlockLocation[] blockLocations = locatedFileStatus.getBlockLocations();
        line.append(Arrays.asList(blockLocations)).append("\t");

        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}

【7】刪除目錄或文件

try {
    boolean delete = fileSystem.delete(new Path("/?"), true); // 第二個參數表示是否遞歸
    System.out.println(delete ? "成功" : "失敗");
} catch (IOException e) {
    e.printStackTrace();
}

【8】上傳文件

// 複製文件
try {
    fileSystem.copyFromLocalFile(new Path("?:\\???"), new Path("/???/???"));
} catch (IOException e) {
    e.printStackTrace();
}

// 剪切文件
try {
    fileSystem.moveFromLocalFile(new Path("?:\\???"), new Path("/???/???"));
} catch (IOException e) {
    e.printStackTrace();
}

// 複製文件,流式方式
File file = new File("?:\\???");
try {
    FileInputStream fis = new FileInputStream(file);
    FSDataOutputStream fsos = fileSystem.create(new Path("/???/???"));
    /*
     * 第三個參數,緩衝容器大小
     * 第四個參數,是否關閉輸入流和輸入流
     */
    IOUtils.copyBytes(fis, fsos, 4096, true);
} catch (IOException e) {
    e.printStackTrace();
}

【9】下載文件

try {
    /*
     * 第一個參數,是否刪除源文件。true: 剪切,false: 複製
     * 最後一個參數,是否將RawLocalFileSystem作爲本地文件系統。推薦使用
     */
    fileSystem.copyToLocalFile(false, new Path("?:\\???"), new Path("/???/???"), true);
} catch (IOException e) {
    e.printStackTrace();
}

// 下載文件,流式方式
try {
    FSDataInputStream fsis = fileSystem.open(new Path("/???/???");
    File file = new File("?:\\???");
    FileOutputStream fos = new FileOutputStream(file);
    /*
     * 第三個參數,緩衝容器大小
     * 第四個參數,是否關閉輸入流和輸入流
     */
    IOUtils.copyBytes(fsis, fos, 4096, true);
} catch (IOException e) {
    e.printStackTrace();
}

// 下載2塊文件
try {
    FSDataInputStream fsis = fileSystem.open(new Path("/???/jdk-8u212-linux-x64.tar.gz"));
    File part1 = new File("?:\\part1");
    FileOutputStream fos1 = new FileOutputStream(part1);

    int bufferSize = 4096; // 緩衝大小
    byte[] buffer = new byte[bufferSize];
    long blockSize = 128 * 1024 * 1024; // 128MB -- 塊大小
    for (int offset = 0; offset < blockSize; offset += bufferSize) {
        int read = fsis.read(buffer, 0, bufferSize);
        fos1.write(buffer, 0, read);
    }
    IOUtils.closeStream(fos1); // 關閉輸出流

    File part2 = new File("?:\\part2");
    FileOutputStream fos2 = new FileOutputStream(part2);
    fsis.seek(blockSize);
    IOUtils.copyBytes(fsis, fos2, bufferSize, true); // 複製塊1,並關閉輸入流和輸出流
} catch (IOException e) {
    e.printStackTrace();
}

【10】重命名目錄或文件(移動操作)

try {
    boolean rename = fileSystem.rename(new Path("/?/?"), new Path("/?/?"));
    System.out.println(rename ? "成功" : "失敗");
} catch (IOException e) {
    e.printStackTrace();
}

【11】判斷是否是文件還是目錄

try {
    Path path = new Path("/?/?");
    boolean file = fileSystem.isFile(path);
    boolean directory = fileSystem.isDirectory(path);
    System.out.println(file ? "是文件" : "不是文件!");
    System.out.println(directory ? "是目錄" : "不是目錄!");
} catch (IOException e) {
    e.printStackTrace();
}

注意:如果目錄或文件不存在時,這2種判斷都會返回false

7、機架感知(Rack Awareness)

 Hadoop具有機架感知的功能。 將一個塊的分片複製品放在不同的機架上,通過這種塊放置的方式,外加使用機架感知來實現容錯。 這可以在羣集發生網絡切換故障或分區時,提供數據的可用性

 就默認分片數爲3而言:如果本機DataNode需要存儲數據,則Hadoop會將DataNode的副本放到本機上,否則就隨機放到其他DataNode上。同時會在另外一機架上的節點放置副本。最後一副本放到同一機架上的不同節點

 這樣做可以減少機架間的寫入流量,從而提高寫入效率。因爲機架故障的可能性遠小於節點故障的可能性,所以這種策略不會影響數據的可靠性和可用性。此外也減少了讀取數據時使用的聚合網絡帶寬,因爲塊放到了2個唯一的機架上,而不是3個。三分之一的副本存放到一個節點上,三分之二的副本存放到一個機架上。這種策略可以提高寫入性能,而不會影響數據的可靠性或可讀性

8、文件上傳和文件下載的工作流程

【1】文件上傳

  1. 客戶端請求NameNode,是否可以進行文件的上傳
  2. 當客戶端收到可以上傳的響應後,依次順序上傳block0(0 ~ 128MB),block1(128MB ~ 256MB)到DataNode
  3. 獲取所有需要上傳的DataNode的節點信息,依次請求建立傳輸通道
  4. 收到DataNode響應,傳輸通道建立完畢,開始傳輸數據

【2】文件下載

  1. 客戶端請求NameNode,獲取block信息
  2. 收到NameNode返回的元數據信息,請求對應的DataNode,獲取所需的block數據
  3. 合併所有的block數據爲一個完整的數據,最終得到完整數據

9、NameNode和SecondaryNameNode工作機制

【1】NameNode啓動時的工作機制

  1. NameNode將鏡像文件(FsImage)加載到內存中
  2. NameNode將編輯日誌(EditLog)加載到內存中
  3. 保存檢查點位置(checkpoint)
  4. 進入安全模式(Safe mode)

這一過程,可以通過NameNode的管理頁面來查看,在Startup Progress選項卡頁面當中


Elapsed Time: ??? sec, Percent Complete: 100%

Phase Completion Elapsed Time
Loading fsimage /???/dfs/name/current/fsimage_??? ??? KB 100% ??? sec
 inodes (???/???) 100%
 delegation tokens (???/???) 100%
 cache pools (???/???) 100%
Loading edits 100% ??? sec
 /???/dfs/name/current/edits_???-??? ??? MB (???/???) 100%
Saving checkpoint 100% ??? sec
 inodes /???/dfs/name/current/fsimage.ckpt_??? (???/???) 100%
 delegation tokens /???/dfs/name/current/fsimage.ckpt_??? (???/???) 100%
 cache pools /???/dfs/name/current/fsimage.ckpt_??? (???/???) 100%
Safe mode 100% ??? sec
 awaiting reported blocks (???/???) 100%

每一個block佔元數據的大小爲150字節,由於NameNode在啓動時,會將這些都加載到內存當中,而內存是有上限的。所以這就是Hadoop不宜存儲過多小文件的原因,它會佔用大量的內存空間

【2】SecondaryNameNode運行時的工作機制

  1. 請求NameNode是否需要檢查點(Checkpoint)
  2. 如果需要Checkpoint,則執行
  3. 複製NameNode的編輯日誌(edits),包括已經執行完畢的(例如:edits_0000000000000000001-0000000000000000002)以及正在執行的(例如:edits_inprogress_0000000000000010453)
  4. 加載到內存中,合併生成新的鏡像文件(fsimage.checkpoint)
  5. 複製鏡像文件(fsimage.checkpoint)到NameNode

 將編輯日誌(EditLog)中的所有事務保存到鏡像文件(FsImage)裏,並加載到內存中,然後結轉舊的編輯日誌(EditLog),這個過程稱爲檢查點(checkpoint)

 觸發SecondaryNameNode請求NameNode查看是否需要執行檢查點(checkpoint),是由2個參數配置來決定的,在etc/hadoop/hdfs-site.xml

<!-- 執行檢查點的週期,單位:秒。默認爲1小時 -->
<property>
    <name>dfs.namenode.checkpoint.period</name>
    <value>3600</value>
</property>
<!-- 執行編輯日誌的事務次數。默認爲一百萬次 -->
<property>
    <name>dfs.namenode.checkpoint.txns</name>
    <value>1000000</value>
</property>

 可以通過前端管理頁面來查看SecondaryNameNode的概覽,鏈接是:http://SecondaryNameNode服務主機的IP:50090

 包括了:Hadoop的版本(Version),NameNode的服務地址(NameNode Address),啓動時間(Started),上次檢查點時間(Last Checkpoint)以及檢查點週期(Checkpoint Period)和HDFS事務執行次數上限(Checkpoint Transactions)

 注意:hadoop-2.9.2版本的SecondaryNameNode管理頁面JS有一個名爲moment的function未定義,所以在加載頁面時不會展示數據。解決辦法是打開瀏覽器的開發者工具裏的Sources選項卡,修改moment函數

'date_tostring' : function (v) {
    // return moment(Number(v)).format('ddd MMM DD HH:mm:ss ZZ YYYY');
    return new Date(v); // 直接新建時間對象並返回即可
},

 在dfs-dust.js:61處打斷點,修改並保存JS,放開斷點即可

【3】NameNode運行時的工作機制

 接收SecondaryNameNode發來的鏡像文件(fsimage.checkpoint),並重命名爲fsimage

10、編輯日誌和鏡像文件

【1】概念

 EditLog文件位於??/dfs/name/current/目錄裏,以edits_開頭。它保存了HDFS的所有更新操作的記錄的序列化信息

 seen_txid文件保存了正在執行過程中的EditLog文件(edits_inprogress_???)的事務ID

 FsImage文件位於??/dfs/name/current/目錄裏,以fsimage_開頭。它保存了HDFS的所有目錄和文件的idnode的序列化信息,是HDFS元數據的檢查點(checkpoint)的持久化形態

 VERSION文件,保存了NameNode的唯一標識(namespaceID),全局唯一的集羣ID(clusterID),以及存儲形態(storageType=NAME_NODE)

 SecondaryNameNode和NameNode相比,其???/dfs/namesecondary/current/目錄裏,僅僅少了正在執行過程中的EditLog文件(edits_inprogress_???)和seen_txid文件。這樣設計是爲了方便當NameNode節點故障時,並且沒有及時備份數據,可以通過SecondaryNameNode來恢復NameNode

【2】反序列化EditLog和FsImage

 可以反序列化編輯日誌,使用命令:bin/hdfs oev -p 反序列化的文件類型 -i 編輯日誌路徑 -o 反序列化後的文件路徑

 可選的反序列化的文件類型有:binary (native binary format that Hadoop uses), xml (default, XML format), stats (prints statistics about edits file)。可以看到默認是XML類型

 反序列化鏡像文件,使用命令:bin/hdfs oiv -p 反序列化的文件類型 -i 鏡像文件路徑 -o 反序列化後的文件路徑

 可選的反序列化的文件類型有:(XML|FileDistribution|ReverseXML|Web|Delimited) The default is Web。可以看到默認是Web類型。推薦使用XML

【3】反序列化後的EditLog

<?xml version="1.0" encoding="UTF-8"?>
<EDITS>
  <!-- EditLog版本號 -->
  <EDITS_VERSION>-63</EDITS_VERSION>
  <RECORD>
    <!-- 操作類型:開始記錄EditLog -->
    <OPCODE>OP_START_LOG_SEGMENT</OPCODE>
    <DATA>
      <!-- 事務ID -->
      <TXID>1</TXID>
    </DATA>
  </RECORD>
  <RECORD>
    <!-- 操作類型:結束記錄EditLog -->
    <OPCODE>OP_END_LOG_SEGMENT</OPCODE>
    <DATA>
      <TXID>2</TXID>
    </DATA>
  </RECORD>
<!-- 另外一個EditLog -->
  <RECORD>
    <!-- 操作類型:創建目錄 -->
    <OPCODE>OP_MKDIR</OPCODE>
    <DATA>
      <TXID>4</TXID>
      <!-- 文件大小:0,目錄 -->
      <LENGTH>0</LENGTH>
      <!-- inodeID -->
      <INODEID>16386</INODEID>
      <!-- 路徑 -->
      <PATH>/tmp</PATH>
      <!-- 創建時間戳 -->
      <TIMESTAMP>1565635741266</TIMESTAMP>
      <!-- 權限 -->
      <PERMISSION_STATUS>
        <!-- 所有者 -->
        <USERNAME>hadoop</USERNAME>
        <!-- 所屬組 -->
        <GROUPNAME>supergroup</GROUPNAME>
        <MODE>504</MODE>
      </PERMISSION_STATUS>
    </DATA>
  </RECORD>
  <RECORD>
    <!-- 操作類型:新建文件 -->
    <OPCODE>OP_ADD</OPCODE>
    <DATA>
      <TXID>10315</TXID>
      <LENGTH>0</LENGTH>
      <INODEID>18322</INODEID>
      <PATH>/???/_temporary/???/_temporary/attempt_1565637203418_0001_r_000000_0/part-r-00000</PATH>
      <!-- 分片數 -->
      <REPLICATION>4</REPLICATION>
      <MTIME>1565637361625</MTIME>
      <ATIME>1565637361625</ATIME>
      <!-- 塊大小:128MB -->
      <BLOCKSIZE>134217728</BLOCKSIZE>    
      <!-- 客戶端名稱 -->
       <CLIENT_NAME>DFSClient_attempt_1565637203418_0001_r_000000_0_950599497_1</CLIENT_NAME>
      <!-- 客戶端IP -->
      <CLIENT_MACHINE>???.???.???.???</CLIENT_MACHINE>
      <OVERWRITE>false</OVERWRITE>
      <PERMISSION_STATUS>
        <USERNAME>hadoop</USERNAME>
        <GROUPNAME>supergroup</GROUPNAME>
        <MODE>420</MODE>
      </PERMISSION_STATUS>
      <RPC_CLIENTID>fb6e1e9c-f8a4-4428-a6f0-12304607e6c1</RPC_CLIENTID>
      <RPC_CALLID>4</RPC_CALLID>
    </DATA>
  </RECORD>
  <RECORD>
    <!-- 操作類型:分配塊ID -->
    <OPCODE>OP_ALLOCATE_BLOCK_ID</OPCODE>
    <DATA>
      <TXID>10316</TXID>
      <BLOCK_ID>1073743480</BLOCK_ID>
    </DATA>
  </RECORD>
  <RECORD>
    <!-- 操作類型:添加塊 -->
    <OPCODE>OP_ADD_BLOCK</OPCODE>
    <DATA>
      <TXID>10318</TXID>
      <PATH>/???/_temporary/???/_temporary/attempt_1565637203418_0001_r_000000_0/part-r-00000</PATH>
      <BLOCK>
        <BLOCK_ID>1073743480</BLOCK_ID>
        <NUM_BYTES>0</NUM_BYTES>
        <GENSTAMP>2656</GENSTAMP>
      </BLOCK>
      <RPC_CLIENTID></RPC_CLIENTID>
      <RPC_CALLID>-2</RPC_CALLID>
    </DATA>
  </RECORD>
  <RECORD>
    <!-- 操作類型:刪除臨時目錄 -->
    <OPCODE>OP_DELETE</OPCODE>
    <DATA>
      <TXID>10324</TXID>
      <LENGTH>0</LENGTH>
      <PATH>/???/_temporary</PATH>
      <TIMESTAMP>1565637361871</TIMESTAMP>
      <RPC_CLIENTID>4e4b34de-f566-4b67-a350-e014ebc27e39</RPC_CLIENTID>
      <RPC_CALLID>39</RPC_CALLID>
    </DATA>
  </RECORD>
  </RECORD>
  <RECORD>
    <!-- 操作類型:重命名(執行MapReduce任務) -->
    <OPCODE>OP_RENAME</OPCODE>
    <DATA>
      <TXID>10354</TXID>
      <LENGTH>0</LENGTH>
      <SRC>/tmp/hadoop-yarn/staging/history/done_intermediate/hadoop/job_1565637203418_0001_conf.xml</SRC>
      <DST>/tmp/hadoop-yarn/staging/history/done/2019/08/13/000000/job_1565637203418_0001_conf.xml</DST>
      <TIMESTAMP>1565637363513</TIMESTAMP>
      <OPTIONS>TO_TRASH</OPTIONS>
      <RPC_CLIENTID>696288b7-d3ff-4bf2-a17b-5dc108925fc9</RPC_CLIENTID>
      <RPC_CALLID>20</RPC_CALLID>
    </DATA>
  </RECORD>
<!-- 另外一個EditLog -->
  <RECORD>
    <!-- 操作類型:重命名(上傳文件) -->
    <OPCODE>OP_RENAME_OLD</OPCODE>
    <DATA>
      <TXID>25</TXID>
      <LENGTH>0</LENGTH>
      <SRC>/???/???._COPYING_</SRC>
      <DST>/???/???</DST>
      <TIMESTAMP>1566211667239</TIMESTAMP>
      <RPC_CLIENTID>a3bffd43-b49b-4c0b-b4d1-394feb9fd9a2</RPC_CLIENTID>
      <RPC_CALLID>8</RPC_CALLID>
    </DATA>
  </RECORD>
</EDITS>

【4】反序列化後的FsImage

<?xml version="1.0"?>
<fsimage>
    <!-- 版本信息 -->
    <version>
        <layoutVersion>-63</layoutVersion>
        <onDiskVersion>1</onDiskVersion>
        <oivRevision>826afbeae31ca687bc2f8471dc841b66ed2c6704</oivRevision>
    </version>
    <!-- NameNode信息 -->
    <NameSection>
        <!-- 對應VERSION文件裏的namespaceID -->
        <namespaceId>598335362</namespaceId>
        <genstampV1>1000</genstampV1>
        <genstampV2>2670</genstampV2>
        <genstampV1Limit>0</genstampV1Limit>
        <lastAllocatedBlockId>1073743492</lastAllocatedBlockId>
        <txid>10455</txid>
    </NameSection>
    <!-- 目錄和文件信息 -->
    <INodeSection>
        <!-- 最後一個InodeId -->
        <lastInodeId>18346</lastInodeId>
        <numInodes>42</numInodes>
        <inode>
            <id>16385</id>
            <!-- 這是個目錄 -->
            <type>DIRECTORY</type>
            <!-- /:根路徑 -->
            <name/>
            <mtime>1566131716332</mtime>
            <!-- 權限,所有者:所屬組:0權限信息 -->
            <permission>hadoop:supergroup:0755</permission>
            <nsquota>9223372036854775807</nsquota>
            <dsquota>-1</dsquota>
        </inode>
        <inode>
            <id>16386</id>
            <type>DIRECTORY</type>
            <!-- 目錄名稱 -->
            <name>tmp</name>
            <mtime>1565636758639</mtime>
            <permission>hadoop:supergroup:0770</permission>
            <nsquota>-1</nsquota>
            <dsquota>-1</dsquota>
            </inode>
        <inode>
            <id>18346</id>
            <!-- 這是個文件 -->
            <type>FILE</type>
            <!-- 文件名稱 -->
            <name>jdk-8u212-linux-x64.tar.gz</name>
            <!-- 分片數量 -->
            <replication>4</replication>
            <mtime>1566132732553</mtime>
            <atime>1566132726115</atime>
            <preferredBlockSize>134217728</preferredBlockSize>
            <permission>hadoop:supergroup:0644</permission>
            <blocks>
                <block>
                    <id>1073743491</id>
                    <genstamp>2669</genstamp>
                    <numBytes>134217728</numBytes>
                </block>
                <block>
                    <id>1073743492</id>
                    <genstamp>2670</genstamp>
                    <numBytes>60795424</numBytes>
                </block>
            </blocks>
            <storagePolicyId>0</storagePolicyId>
        </inode>
    </INodeSection>
    <INodeReferenceSection/>
    <!-- 快照信息 -->
    <SnapshotSection>
        <snapshotCounter>0</snapshotCounter>
        <numSnapshots>0</numSnapshots>
    </SnapshotSection>
    <!-- 目錄的父子關係 -->
    <INodeDirectorySection>
        <directory>
            <parent>16385</parent>
            <child>18340</child>
            <child>16392</child>
            <child>16386</child>
        </directory>
        <directory>
            <parent>16386</parent>
            <child>16387</child>
            <child>18243</child>
        </directory>
    </INodeDirectorySection>
    <FileUnderConstructionSection/>
    <SecretManagerSection>
        <currentId>0</currentId>
        <tokenSequenceNumber>0</tokenSequenceNumber>
        <numDelegationKeys>0</numDelegationKeys>
        <numTokens>0</numTokens>
    </SecretManagerSection>
    <CacheManagerSection>
        <nextDirectiveId>1</nextDirectiveId>
        <numDirectives>0</numDirectives>
        <numPools>0</numPools>
    </CacheManagerSection>
</fsimage>

11、滾動編輯日誌

 使用命令bin/hdfs dfsadmin -rollEdits,這樣會結轉當前正在執行中的EditLog(edits_???-???),並生成一個新的正在執行過程中的EditLog(edits_inprogress_???

Successfully rolled edit logs.
New segment starts at txid ???

12、通過SecondaryNameNode來恢復NameNode

【1】手動方式

  1. 模擬NameNode故障,jps查看NameNode的進程號,通過命令kill -9 NameNode的進程ID強制停止NameNode
  2. 首先刪除???/dfs/name目錄裏的所有文件
  3. 使用scp複製SecondaryNameNode的???/dfs/namesecondary目錄裏的所有文件到NameNode的???/dfs/name目錄裏,並刪除複製後的name目錄裏的in_use.lock文件
  4. 啓動NameNode,sbin/hadoop-daemon.sh start namenode
  5. 查看是否可以訪問NameNode的前端管理頁面,並測試HDFS的文件上傳和文件下載

【2】命令方式

  1. 模擬NameNode故障,jps查看NameNode的進程號,通過命令kill -9 NameNode的進程ID強制停止NameNode

  2. 修改NameNode節點的etc/hadoop/hdfs-site.xml文件

    <!-- 修改執行檢查點的週期爲2分鐘,這是爲了保證能夠及時同步SecondaryNameNode的數據。默認爲1小時 -->
    <property>
        <name>dfs.namenode.checkpoint.period</name>
        <value>120</value>
    </property>
    <!-- 指定NameNode的name目錄的路徑。默認爲file://${hadoop.tmp.dir}/dfs/name -->
    <property>
        <name>dfs.namenode.name.dir</name>
        <value>/???/dfs/name</value>
    </property>
    
  3. 使用scp複製SecondaryNameNode的???/dfs/namesecondary目錄到NameNode的???/dfs目錄下,和???/dfs/name平級即可。並刪除複製後的namesecondary目錄裏的in_use.lock文件

  4. 刪除???/dfs/name目錄裏的所有文件

  5. 執行導入檢查點數據命令,bin/hdfs namenode -importCheckpoint

    執行命令後,會發現NameNode被啓動起來。命令執行過程較長,如果此時訪問NameNode的前端管理頁面,會發現提示信息:Upgrade in progress. Not yet finalized.

    當看到process的信息後,就可以Ctrl + C停止命令了

  6. 啓動NameNode,sbin/hadoop-daemon.sh start namenode

  7. 查看是否可以訪問NameNode的前端管理頁面,並測試HDFS的文件上傳和文件下載

13、安全模式(Safemode)

【1】概念

 當NameNode在剛啓動時,會進入安全模式,此時HDFS對於客戶端來說是隻讀的

 在系統正常運行期間,NameNode會在內存中保存所有的塊位置的映射信息。而在安全模式下,每個DataNode會向NameNode發送最新的塊列表信息。NameNode瞭解的塊位置信息越多,它可越高效地運行文件系統

一旦滿足最小副本條件,NameNode會在30秒後退出安全模式。最小副本條件是指,在整個文件系統當中,99.9%的塊滿足最小副本級別,最小副本級別可以通過etc/hadoop/hdfs-site.xmldfs.replication.min參數來配置,默認值爲1

 啓動一個剛格式化的HDFS集羣時,不會進入安全模式,是因爲此時還沒有任何塊信息

【2】相關命令

  • 查看當前是否在安全模式當中

    bin/hdfs dfsadmin -safemode get

  • 進入安全模式

    bin/hdfs dfsadmin -safemode enter

  • 退出安全模式

    bin/hdfs dfsadmin -safemode leave

  • 等待安全模式退出

    bin/hdfs dfsadmin -safemode wait

#!/bin/bash
bin/hdfs dfsadmin -safemode wait
echo "退出安全模式!"
# 執行HDFS文件操作
# bin/hadoop fs -???

14、NameNode多目錄配置

 在etc/hadoop/hdfs-site.xml中配置

<!-- 指定NameNode的name目錄的路徑,多個目錄之間用逗號隔開。默認爲file://${hadoop.tmp.dir}/dfs/name -->
<property>
    <name>dfs.namenode.name.dir</name>
    <value>file://${hadoop.tmp.dir}/dfs/name1,file://${hadoop.tmp.dir}/dfs/name2</value>
</property>

注意:配置此項後,再次啓動集羣時需要格式化NameNode

15、DataNode工作機制

  1. DataNode啓動後,向NameNode發起註冊
  2. 註冊成功後,每隔1個小時上傳一次所有塊的位置信息
  3. NameNode每隔3秒鐘(可配置),執行一次心跳檢測,DataNode接收到心跳檢測並返回給NameNode發來的命令信息
  4. 如果超過10分鐘(可配置)[外加30秒],NameNode沒有收到DataNode傳來的心跳檢測信息,則認爲該節點不可用

 DataNode不同於NameNode,其無需進行格式化,在初始化過程中,創建好數據目錄

 DataNode保存了數據,數據長度(文件大小),校驗和(CRC),以及時間戳等信息。這些位於???/dfs/data/current目錄當中

 DataNode校驗和使用CRC算法(循環冗餘校驗)。對原始數據進行CRC的校驗計算,然後和傳輸過來的CRC校驗位進行對比,查看結果是否一致

 心跳檢測週期和心跳超時時間,可以在etc/hadoop/hdfs-site.xml中來配置

<!-- 心跳檢測DataNode的超時時間,單位:毫秒。默認爲300秒(5分鐘) -->
<property>
    <name>dfs.namenode.heartbeat.recheck-interval</name>
    <value>300000</value>
</property>
<!-- DataNode的心跳檢測間隔,單位:秒。默認爲3秒 -->
<property>
    <name>dfs.heartbeat.interval</name>
    <value>3</value>
</property>

心跳超時時間 = 2 * dfs.namenode.heartbeat.recheck-interval + 10 * dfs.heartbeat.interval

 所以默認心跳超時時間是10分鐘

 一旦NameNode將最近沒有發送heartbeat消息的DataNode標記爲已死亡,就不會再將新的IO請求轉發給它,並且對於HDFS來說,它是不可用的。這就有可能降低某些block的分片數量(replication factor)

16、DataNode多目錄配置

 在etc/hadoop/hdfs-site.xml中配置

<!-- 指定NameNode的name目錄的路徑,多個目錄之間用逗號隔開。默認爲file://${hadoop.tmp.dir}/dfs/data -->
<property>
    <name>dfs.datanode.data.dir</name>
    <value>file://${hadoop.tmp.dir}/dfs/data1,file://${hadoop.tmp.dir}/dfs/data2</value>
</property>

注意:配置此項後,再次啓動集羣時需要格式化NameNode

17、集羣節點的動態增加和動態移除

【1】動態新增節點

  1. 準備好新的機器,配置好靜態IP,host和防火牆,以及Java運行環境和環境變量,並新建執行Hadoop的用戶

  2. 使用scp命令複製NameNode服務節點的Hadoop目錄到新的機器上

  3. 配置好其他節點的host

  4. 使用命令ssh-copy-id 用戶名@新機器host,爲NameNode節點和ResourceManager節點,創建新的機器的免密登錄

  5. ① 在所有節點的etc/hadoop目錄下,創建名爲dfs.hosts的文件,並添加所有節點的IP到文件中。每個IP佔一行,不允許有空格

    ② 在所有節點的etc/hadoop/hdfs-site.xml中,修改配置

    <!-- 修改 -->
    <property>
        <!-- 配置副本數 -->
        <name>dfs.replication</name>
        <value>增加機器後的DataNode的數量</value>
    </property>
    <!-- 新增 -->
    <property>
        <!-- 指定可以連接到NameNode的主機列表。如果該值爲空,則允許所有主機。默認爲空 -->
        <name>dfs.hosts</name>
        <value>/???/可以連接到NameNode的主機列表文件</value>
    </property>
    

    ③ 在所有節點的etc/hadoop/slaves中,將新機器的IP添加到裏面

  6. 在NameNode節點機器上,執行命令bin/hdfs dfsadmin -refreshNodes,刷新DataNode

  7. 在ResourceManager節點機器上,執行命令bin/yarn rmadmin -refreshNodes,刷新NodeManager

  8. 在NameNode節點機器上,執行命令sbin/start-balancer.sh,實現集羣數據的平衡(Blocks)

  9. 此時查看NameNode的前端管理頁面,在Datanodes選項頁面裏,應該新增了一個DataNode節點,其Admin State爲Dead

  10. 到新機器上,分別執行命令sbin/hadoop-daemon.sh start datanode啓動DataNode,和命令sbin/yarn-daemon.sh start nodemanager啓動NodeManager

  11. 再回到NameNode的前端管理頁面,刷新可以看到,新節點的Admin State爲In Service

  12. 上傳文件和下載文件,進行測試

【2】動態移除節點

  1. ① 在所有節點的etc/hadoop目錄下,創建名爲dfs.hosts.exclude的文件,並添加要移除的節點的IP到文件中。每個IP佔一行,不允許有空格

    ② 在所有節點的etc/hadoop/hdfs-site.xml中,修改配置

    <!-- 修改 -->
    <property>
        <!-- 配置副本數 -->
        <name>dfs.replication</name>
        <value>移除機器後的DataNode的數量</value>
    </property>
    <!-- 新增 -->
    <property>
        <!-- 指定不允許連接到NameNode的主機列表。如果該值爲空,則不排除任何主機。默認爲空 -->
        <name>dfs.hosts.exclude</name>
        <value>/???/不允許連接到NameNode的主機列表文件</value>
    </property>
    

    ③ 在所有節點的etc/hadoop/slaves中,將準備移除的機器的IP刪除

  2. 在NameNode節點機器上,執行命令bin/hdfs dfsadmin -refreshNodes,刷新DataNode

  3. 在ResourceManager節點機器上,執行命令bin/yarn rmadmin -refreshNodes,刷新NodeManager

  4. 在NameNode節點機器上,執行命令sbin/start-balancer.sh,實現集羣數據的平衡(Blocks)

  5. 此時查看NameNode的前端管理頁面,在Datanodes選項頁面裏,那些準備移除的節點機器的Admin State應該爲Decommission In Progress。等待數據傳輸給其他的DataNode完畢,狀態變爲Decommission

  6. 在準備退役的機器上,分別執行命令sbin/yarn-daemon.sh stop nodemanager停止NodeManager,和命令sbin/hadoop-daemon.sh stop datanode停止DataNode

  7. 如果在etc/hadoop/hdfs-site.xml中配置了dfs.hosts,則還應刪掉對應的退役機器的IP,並執行命令重新刷新DataNode和NodeManager

18、集羣間的數據拷貝

 使用命令,bin/hadoop distcp hdfs://源服務機器IP:9000/?/? hdfs://目標服務機器IP:9000/?/?

 注意:數據拷貝的時候,會執行一個MapReduce任務,所以必須確保2個集羣的相關資源(YARN等)都在正常運行中

19、數據的歸檔

 由於NameNode啓動時會加載所有的元數據到內存當中,所以過多的小文件,會佔用大量的內存資源。我們可以把這些小文件歸檔(壓縮)起來,這樣就能節省很多的內存空間

 數據歸檔本質上是一個MapReduce程序(確保YARN相關資源已啓動)

 相關命令:

  1. 創建歸檔文件,bin/hadoop archive -archiveName 歸檔文件名.har -p 要歸檔的目錄 歸檔文件輸出目錄

  2. 查看歸檔文件,bin/hadoop fs -ls -R har:///???/???.har

  3. 提取歸檔文件,其實就是複製歸檔文件裏的內容,bin/hadoop fs -cp har:///???/歸檔文件名.har/歸檔文件 /目標路徑

    注意:只能複製歸檔文件的內容,不能移動(mv),否則會報錯:mv: `har:///???/???.har/???': Does not match target filesystem

20、快照

 快照相當於是對目錄進行一個備份,它並不會立即複製所有的文件,而是先創建一個索引文件指向目標文件。當有新的文件寫入時,它纔會產生新的文件

 相關命令:

  1. 開啓指定目錄的快照功能,bin/hdfs dfsadmin -allowSnapshot /指定目錄

  2. 創建指定目錄的快照,bin/hdfs dfs -createSnapshot /指定目錄 快照文件名稱

    快照文件名稱可以省略,默認的名稱爲/指定目錄/.snapshot/s日期-時間.毫秒

  3. 重命名快照,bin/hdfs dfs -renameSnapshot /快照目錄 舊名稱 新名稱

    注意:系統默認創建的快照名稱是無法重命名的,會報錯:renameSnapshot: Modification on a read-only snapshot is disallowed

  4. 展示當前用戶下的所有的已開啓快照功能的目錄,bin/hdfs lsSnapshottableDir

  5. 比較兩個快照目錄的不同,bin/hdfs snapshotDiff /開啓快照功能的目錄 快照名稱1 快照名稱2

    快照名稱可以使用.來代替,表示當前開啓了快照功能的目錄

    注意:比較結果是用右邊的快照和左邊的快照相比較

  6. 刪除快照,bin/hdfs dfs -deleteSnapshot /開啓快照功能的快照目錄 快照名稱

    注意快照目錄和快照名稱之間的空格

  7. 禁用指定目錄的快照功能,bin/hdfs dfsadmin -disallowSnapshot /指定目錄

    注意:禁用快照功能前,需要先刪除當前目錄的所有快照,否則報錯:disallowSnapshot: The directory /???/??? has snapshot(s). Please redo the operation after removing all the snapshots.

 使用快照進行恢復,則使用bin/hadoop fs -cp /快照目錄裏的文件 /指定目錄。注意不能使用移動(mv)命令,因爲快照文件是隻讀的

21、回收站

 回收站功能默認是關閉的,需要在etc/hadoop/core-site.xml配置文件中,設置2個參數來開啓它

<!-- 回收站內的文件的有效期,單位:分鐘。默認爲0,表示禁用回收站功能 -->
<property>
    <name>fs.trash.interval</name>
    <value>0</value>
</property>
<!--
檢查回收站的週期,單位:分鐘。該值要小於等於fs.trash.interval。如果爲0,則設置爲fs.trash.interval的值。
每當回收站運行的時候,它都會創建一個新的檢查點,用於刪除超過fs.trash.interval時間(文件有效期)之後的文件
-->
<property>
    <name>fs.trash.checkpoint.interval</name>
    <value>0</value>
</property>

 同時還要注意:默認擁有查看回收站的權限的用戶名是dr.who,需要修改它爲操作Hadoop集羣的用戶。否則訪問NameNode的前端管理頁面時會報錯:Permission denied: user=dr.who, access=READ_EXECUTE, inode="/user":???:supergroup:drwx------

 在etc/hadoop/core-site.xml

<!-- 擁有查看HDFS Web頁面(NameNode前端管理頁面)的權限的用戶名 -->
<property>
    <name>hadoop.http.staticuser.user</name>
    <value>dr.who</value>
</property>

 刪除的文件目錄,位於回收站的目錄/user/執行Hadoop的用戶名/.Trash/Current/???

 恢復回收站裏的文件的方法是,將回收站裏的文件移動出來,使用命令bin/hadoop fs -mv /回收站目錄 /恢復目錄。注意,如果移動文件夾,可能因爲權限問題而無法查看

 刪除回收站目錄,可以使用命令bin/hadoop fs -rm -r -skipTrash /user/???。加上-skipTrash選項

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章