本章內容介紹下 Hadoop 自帶的分布式文件系統,HDFS 即 Hadoop Distributed Filesystem。HDFS 能夠存儲超大文件,可以部署在廉價的服務器上,適合一次寫入多次讀取的場景。但 HDFS 不適合低延遲,存儲大量小文件以及修改文件內容的場景。HDFS 應用比較廣泛,如:MR任務、Spark任務、Hive 數據倉庫以及 Hbase 數據庫,它們的底層存儲都可以基於 HDFS 。本章將介紹 HDFS 集群的架構設計以及相關的重要概念。
HDFS的設計以及概念
HDFS集群是典型的 master/slave 架構,master 節點叫做 NameNode,salve 節點叫做 DataNode。最簡單的 HDFS 集群便是一個 NameNode 節點和多個 DataNode 節點,HDFS 集群的架構圖如下

Block:數據塊,HDFS 集群將存儲的文件划分為多個分塊,塊作為獨立的存儲單元,默認大小為為 128M。如果某個文件超過集群單機存儲容量,分塊可以解決該問題;其次按照塊進行存儲、備份能簡化系統的設計。默認塊大小修改 hdfs-site.xml 文件中的 dfs.blocksize 配置。
NameNode:HDFS 集群的 Master 節點,維護集群文件的目錄結構(命名空間)和編輯日志文件,同時在內存中記錄文件各個塊所在的數據節點的信息。
DataNode:HDFS 集群的 Slave 節點,負責存儲實際的數據。根據需要存儲和檢索數據塊,並定期向 NameNode 發送他們所存儲的數據塊列表。為了實現數據存儲的高可靠,HDFS 將一個塊存儲在不同的 DataNode 節點, 默認是 3 個,可以通過 hdfs-site.xml 文件中的 dfs.replication 配置修改默認值。如果當前 DataNode 中的數據塊損壞, 可以從其他 DataNode 節點復制一個正確的數據塊。
以上是架構圖中顯而易見的幾個重要概念,接下來將結合架構設計中的高可用、可擴展性來介紹下架構圖中隱藏的幾個重要概念。
聯邦 HDFS:這個主要是為了解決可擴展性的問題,我們知道 NameNode 進程的內存中存放了數據與數據位置的對應關系,對於一個文件數據量多的集群來說,NameNode 的內存將成為集群規模擴大的瓶頸。因此,單一 NameNode 的集群並不可取。Hadoop 2.x 的發行版引入了聯邦 HDFS 允許向集群中添加 NameNode 節點實現橫向擴展。每一個 NameNode 管理命名空間中的一部分,每個 NameNode 維護一個命名空間卷(namespace volume),命名空間卷之間相互獨立,一個 NameNode 失效不會影響其他 NameNode 維護的命名空間。
HDFS HA: 這個解決高可用,即 HDFS High Available。這一實現中配置了一對活動-備用(active-standby)NameNode。當活動的 NameNode 失效,備用 NameNode 會接管相應的任務,這一過程對用戶透明。實現這一設計,需要在架構上做如下修改:
1. HA 的兩個 NameNode 之間通過高可用共享存儲實現編輯日志的共享,目的是為了能夠使備用 NameNode 接管工作后實現與主 NameNode 狀態同步。QJM(日志管理器,quorum journal manager)是為提供一個高可用的日志編輯而設計的,被推薦用於大多數 HDFS 集群中。QJM 以一組日志節點的形式運行,一般是 3,每一次編輯必須寫入多數日志節點,因此系統可以忍受任何一個節點丟失,日志節點便是 JournalNode。
2. DataNode 需要同時向 2 個 NameNode 發送數據報告,因為數據塊的映射信息存儲在 NameNode 的內存中
3. 客戶端需要處理 NameNode 失效的問題,對用戶透明
HDFS的基本操作
命令行接口
命令行接口操作 HDFS 是最簡單、最方便的方式。HDFS 的命令與 Linux 本地命令非常相似,可以通過 hadoop fs help 命令查看 HDFS 所支持所有命令,接下來介紹下常用的命令
hadoop fs -put <localsrc> <dst> #將本地文件上傳至 HDFS hadoop fs -ls <path> # 與 Linux ls命令類似 hadoop fs -cat <src> #查看 HDFS 文件數據 hadoop fs -text <path> # 同 cat 命令, 可以看 SequenceFile、壓縮文件
hadoop fs -rm <src> # 刪除 HDFS 文件或目錄
以上是比較常用的 HDFS 命令,查看幫助文檔可以在每個命令上增加一些命令行選項,輸出不同的信息。以 ls 命令為例,看一下 HDFS 輸出的文件信息
hadoop fs -ls /hadoop-ex/wordcount/input
-rw-r--r-- 3 root supergroup 32 2019-03-03 01:34 /hadoop-ex/wordcount/input/words
-rw-r--r-- 3 root supergroup 28 2019-03-03 01:46 /hadoop-ex/wordcount/input/words2
可以發現輸出的內容與 Linux 下 ls 命令類似。第 1 部分顯示文件類型與權限,第 2 部分是副本數量 3,第 3 、4部分是所屬的用戶和用戶組,第 5 部分是文件大小,若是目錄則為 0 ,第 6、7 部分是文件的修改日期和時間,第 8 部分是文件的路徑和名稱。 在 HDFS 中有個超級用戶,即 啟動 NameNode 的用戶。
Java 接口
相對於命令行接口,Java接口更加靈活,更強大。但用起來不是很方便,一般可以在 MR 或者 Spark 任務中使用 Java 接口讀取 HDFS 上的數據。本章僅舉一個讀取 HDFS 文件數據的例子介紹一下 Java 接口的使用方式,主要使用 FileSystem API 來實現,更具體和更多的使用方法讀者可以自行查閱。
package com.cnblogs.duma.hdfs; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IOUtils; import java.io.IOException; import java.io.InputStream; import java.net.URI; public class FileSystemEx { public static void main(String[] args) throws IOException { Configuration conf = new Configuration(); // uri 便是 core-site.xml 文件中 fs.defaultFS 配置的值 FileSystem fs = FileSystem.get(URI.create("hdfs://hadoop0:9000"), conf); InputStream in = null; try { // 指定打開的文件 in = fs.open(new Path("/hadoop-ex/wordcount/input/words")); // 將輸入流拷貝到標准輸出流 IOUtils.copyBytes(in, System.out, 4096, false); } catch (IOException e) { e.printStackTrace(); } finally { // 關閉輸入流 IOUtils.closeStream(in); } } }
小結
本章主要介紹了 HDFS 的架構設計和一些重要的概念,這些設計上的東西可能對我們自己設計架構或者寫代碼會有幫助。所以,學習一個框架不光會用,更要注重他的架構設計,以及跟其他架構對比的優缺點,這對我們以后的成長有很大的幫助。最后花了少量篇幅介紹了 HDFS 的基本操作,這方便文檔比較全,並且並不復雜,因此沒有詳細的介紹。個人認為 HDFS Java 接口應用場景相對有限,日后跟 HDFS 打交道更多的可能還是命令行接口。
