1、分布式文件系統
計算機集群結構
分布式文件系統把文件分布存儲到多個節點(計算機)上,成千上萬的計算機節點構成計算機集群。
分布式文件系統使用的計算機集群,其配置都是由普通硬件構成的,與用多個處理器和專用高級硬件的並行化處理裝置相比,前者大大降低了硬件上的開銷。
分布式文件系統的結構
分布式文件系統在物理結構上是由眾多階段及節點構成的,而這些節點中分為兩類。一類是主節點(Master Node),又被稱為名稱節點(NameNode),另一類是從節點(Slave Node),又被稱為數據節點(DataNode)。
2、HDFS簡介
官方用戶指南:http://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-hdfs/HdfsUserGuide.html
Hadoop是由HDFS和MapReduce兩大組件組成的,HDFS全稱為Hadoop Distributed File System(Hadoop 分布式文件系統)。
它和現有的分布式文件系統有很多共同點。但同時,它和其他的分布式文件系統的區別也是很明顯的。HDFS是一個高度容錯性的系統,適合部署在廉價的機器上。HDFS能提供高吞吐量的數據訪問,非常適合大規模數據集上的應用。
HDFS要實現的目標:
- 兼容廉價的硬件設備
- 流數據讀寫
- 大數據集
- 簡單的文件類型
- 強大的跨平台兼容性
HDFS局限性:
- 不適合低延遲數據訪問
- 無法高效存儲大量小文件(與自身實現有關)
- 不支持多用戶寫入及任意修改文件
3、HDFS相關概念
塊
“塊”在HDFS中作為最小存儲單位,默認一個塊為64MB。在HDFS中,一個文件將會被分割成多個塊,保存到各個數據節點。塊的大小遠遠高於普通文件系統,可以最小化尋址開銷。
HDFS中抽象的塊模型可以帶來如下好處:
- 支持大規模文件存儲
單個文件被分成若干個塊,分別存儲到若干個數據節點中,其文件大小不會受到單個節點容量的限制。
- 簡化系統設計
文件塊大小是固定的,可以很容易計算出一個節點中可以存儲多少個文件塊。方便了元數據的管理,元數據不需要和文件塊一起保存,可以由其它系統負責管理元數據。
- 適合數據備份
每個文件塊都可以冗余的存儲到多個數據節點上,當一個節點數據出錯時,就可以根據其他副本節點恢復數據。大大提高了系統的容錯性與高可用性。
名稱節點(NameNode)和數據節點(DataNode)
NameNode與SecondaryNameNode同為“名稱節點”。SecondaryNameNode作為二級名稱節點,它與NameNode的關系是:SecondaryNameNode是NameNode的冷備份。
屬性 | 功能 | 位置 | 內容 |
---|---|---|---|
NameNode | 存儲元數據 | 元數據保存在內存中 | 保存文件、block、DataNode之間的映射關系 |
DataNode | 存儲文件內容 | 文件內容保存到磁盤 | 維護了block id到DataNode本地文件的映射關系 |
名稱節點的數據結構
在HDFS中,名稱節點(NameNode)負責管理分布式文件系統的命名空間 (Namespace),保存了兩個核心的數據結構,即FsImage和EditLog 。名稱節點記錄了每個文件中各個塊所在的數據節點的位置信息。
- FsImage
用於維護文件系統樹以及文件樹中所有的文件和文件夾的元數據 。
- EditLog
操作日志文件,其中記錄了所有針對文件的創建、刪除、重命名等操作 。
FsImage
FsImage文件包含文件系統中所有目錄和文件inode的序列化形式。每個inode是一 個文件或目錄的元數據的內部表示,並包含此類信息:文件的復制等級、修改和訪問 時間、訪問權限、塊大小以及組成文件的塊。對於目錄,則存儲修改時間、權限和配 額元數據 。
FsImage文件沒有記錄塊存儲在哪個數據節點。而是由名稱節點把這些映射保留在 內存中,當數據節點加入HDFS集群時,數據節點會把自己所包含的塊列表告知給名 稱節點,此后會定期執行這種告知操作,以確保名稱節點的塊映射是最新的。
名稱節點的啟動
在名稱節點啟動的時候,它會將FsImage文件中的內容加載到內存中,之后再執行 EditLog文件中的各項操作,使得內存中的元數據和實際的同步,存在內存中的元數 據支持客戶端的讀操作。
一旦在內存中成功建立文件系統元數據的映射,則創建一個新的FsImage文件和一個空的EditLog文件。
名稱節點起來之后,HDFS中的更新操作會重新寫到EditLog文件中,因為FsImage 文件一般都很大(GB級別的很常見),如果所有的更新操作都往FsImage文件中添 加,這樣會導致系統運行的十分緩慢,但是,如果往EditLog文件里面寫就不會這樣 ,因為EditLog 要小很多。每次執行寫操作之后,且在向客戶端發送成功代碼之前, edits文件都需要同步更新。
名稱節點運行期間EditLog不斷變大的問題
在名稱節點運行期間,HDFS的所有更新操作都是直接寫到EditLog中,久而久之, EditLog文 件將會變得很大 。
雖然這對名稱節點運行時候是沒有什么明顯影響的,但是,當名稱節點重啟的時候,名稱節點 需要先將FsImage里面的所有內容映像到內存中,然后再一條一條地執行EditLog中的記錄,當EditLog文件非常大的時候,會導致名稱節點啟動操作非常慢,而在這段時間內HDFS系統處於安全模式,一直無法對外提供寫操作,影響了用戶的使用。
名稱節點運行期間EditLog不斷變大的問題,如何解決?答案是:SecondaryNameNode第二名稱節點。
第二名稱節點是HDFS架構中的一個組成部分,它是用來保存名稱節點中對HDFS元數據信息的備份,並減少名稱節點重啟的時間。SecondaryNameNode一般是單獨運行在一台機器上。
SecondaryNameNode的工作情況:
(1)SecondaryNameNode會定期和NameNode 通信,請求其停止使用EditLog文件,暫時將新的寫操作寫到一個新的文件edit.new上來,這個操作是瞬間完成,上層寫日志的函數完全感覺不到差別。
(2)SecondaryNameNode通過HTTP GET方式從NameNode上獲取到FsImage和EditLog文件,並下載到本地的相應目錄下。
(3)SecondaryNameNode將下載下來的FsImage載入到內存,然后一條一條地執行EditLog文件中的各項更新操作,使得內存中的 FsImage保持最新;這個過程就是EditLog和 FsImage文件合並。
(4)SecondaryNameNode執行完(3)操作之后,會通過post方式將新的FsImage文件發送到NameNode節點上 。
(5)NameNode將從SecondaryNameNode接收到的新的FsImage替換舊的FsImage文件, 同時將edit.new替換EditLog文件,通過這個過程EditLog就變小了。
數據節點(DataNode)
數據節點是分布式文件系統HDFS的工作節點,負責數據的存儲和讀取,會根據客 戶端或者是名稱節點的調度來進行數據的存儲和檢索,並且向名稱節點定期發送自己 所存儲的塊的列表 。
每個數據節點中的數據會被保存在各自節點的本地Linux文件系統中。
4、HDFS體系結構
概述
HDFS采用了主從(Master/Slave)結構模型,一個HDFS集群包括一個名稱節點( NameNode)和若干個數據節點(DataNode)。名稱節點作為中心服務器, 負責管理文件系統的命名空間及客戶端對文件的訪問。集群中的數據節點一般是一個節點運行 一個數據節點進程,負責處理文件系統客戶端的讀/寫請求,在名稱節點的統一調度下進行數據 塊的創建、刪除和復制等操作。每個數據節點的數據實際上是保存在本地Linux文件系統中的。
HDFS命名空間管理
HDFS的命名空間包含目錄、文件和塊。
在HDFS1.0體系結構中,在整個HDFS集群中只有一個命名空間,並且只有唯一一個名稱節點,該節點負責對這個命名空間進行管理 。
HDFS使用的是傳統的分級文件體系,因此,用戶可以像使用普通文件系統一樣,創建、刪除目錄和文件,在目錄間轉移文件,重命名文件等。
通信協議
HDFS是一個部署在集群上的分布式文件系統,因此,很多數據需要通過網絡進行傳輸。
所有的HDFS通信協議都是構建在TCP/IP協議基礎之上的。
客戶端通過一個可配置的端口向名稱節點主動發起TCP連接,並使用客戶端協議與 名稱節點進行交互。
名稱節點和數據節點之間則使用數據節點協議進行交互。
客戶端與數據節點的交互是通過RPC(Remote Procedure Call)來實現的。在設 計上,名稱節點不會主動發起RPC,而是響應來自客戶端和數據節點的RPC請求。
客戶端
客戶端是用戶操作HDFS最常用的方式,HDFS在部署時都提供了客戶端。
HDFS客戶端是一個庫,暴露了HDFS文件系統接口,這些接口隱藏了HDFS實現中的大部分復雜性。
嚴格來說,客戶端並不算是HDFS的一部分。
客戶端可以支持打開、讀取、寫入等常見的操作,並且提供了類似Shell的命令行方式來訪問HDFS中的數據
此外,HDFS也提供了Java API,作為應用程序訪問文件系統的客戶端編程接口。
HDFS體系結構的局限性
HDFS只設置唯一一個名稱節點,這樣做雖然大大簡化了系統設計,但也帶來了一些 明顯的局限性,具體如下:
(1)命名空間的限制:名稱節點是保存在內存中的,因此,名稱節點能夠容納的 對象(文件、塊)的個數會受到內存空間大小的限制。
(2)性能的瓶頸:整個分布式文件系統的吞吐量,受限於單個名稱節點的吞吐量。
(3)隔離問題:由於集群中只有一個名稱節點,只有一個命名空間,因此,無法 對不同應用程序進行隔離。
(4)集群的可用性:一旦這個唯一的名稱節點發生故障,會導致整個集群變得不 可用。
5、HDFS存儲原理
冗余數據保存
作為一個分布式文件系統,為了保證系統的容錯性和可用性,HDFS采用了多副 本方式對數據進行冗余存儲,通常一個數據塊的多個副本會被分布到不同的數據節點 上,如圖所示,數據塊1被分別存放到數據節點A和C上,數據塊2被存放在數據節 點A和B上。
這種多副本方式具有以下幾個優點:
(1)加快數據傳輸速度。
(2)容易檢查數據錯誤。
(3)保證數據可靠性。
數據存取策略
數據存放
Block的副本放置策略:
第一個副本:放置在上傳文件的數據節點;如果是集群外提交,則隨機挑選一台磁盤 不太滿、CPU不太忙的節點。
第二個副本:放置在與第一個副本不同的機架的節點上。
第三個副本:與第一個副本相同機架的其他節點上。
更多副本:隨機節點。
數據讀取
HDFS提供了一個API可以確定一個數據節點所屬的機架ID,客戶端也可以調用API 獲取自己所屬的機架ID。
當客戶端讀取數據時,從名稱節點獲得數據塊不同副本的存放位置列表,列表中包 含了副本所在的數據節點,可以調用API來確定客戶端和這些數據節點所屬的機架ID, 當發現某個數據塊副本對應的機架ID和客戶端對應的機架ID相同時,就優先選擇該副本讀取數據,如果沒有發現,就隨機選擇一個副本讀取數據。
數據錯誤與恢復
HDFS具有較高的容錯性,可以兼容廉價的硬件,它把硬件出錯看作一種常態, 而不是異常,並設計了相應的機制檢測數據錯誤和進行自動恢復,主要包括以下幾種 情形:名稱節點出錯、數據節點出錯和數據出錯。
名稱節點出錯
名稱節點保存了所有的元數據信息,其中,最核心的兩大數據結構是FsImage和Editlog,如果這兩個文件發生損壞,那么整個HDFS實例將失效。因此,HDFS設置了備份機制,把這些核心文件同步復制到備份服務器SecondaryNameNode上。當名稱節點出錯時,就可以根據備份服務器SecondaryNameNode中的FsImage和Editlog數據進行恢復。
數據節點出錯
每個數據節點會定期向名稱節點發送“心跳”信息,向名稱節點報告自己的狀態。
當數據節點發生故障,或者網絡發生斷網時,名稱節點就無法收到來自一些數據節點的心跳信息,這時,這些數據節點就會被標記為“宕機”,節點上面的所有數據都會被標記為“不可讀”,名稱節點不會再給它們發送任何I/O請求。
這時,有可能出現一種情形,即由於一些數據節點的不可用,會導致一些數據塊的副本數量小於冗余因子。
名稱節點會定期檢查這種情況,一旦發現某個數據塊的副本數量小於冗余因子,就會啟動數據冗余復制,為它生成新的副本。
HDFS和其它分布式文件系統的最大區別就是可以調整冗余數據的位置。
數據出錯
網絡傳輸和磁盤錯誤等因素,都會造成數據錯誤。
客戶端在讀取到數據后,會采用md5和sha1對數據塊進行校驗,以確定讀取到正確的數據。
在文件被創建時,客戶端就會對每一個文件塊進行信息摘錄,並把這些信息寫入到同一個路徑的隱藏文件里面。
當客戶端讀取文件的時候,會先讀取該信息文件,然后,利用該信息文件對每個讀 取的數據塊進行校驗,如果校驗出錯,客戶端就會請求到另外一個數據節點讀取該文件塊,並且向名稱節點報告這個文件塊有錯誤,名稱節點會定期檢查並且重新復制這個塊。
6、HDFS讀寫過程
FileSystem是一個通用文件系統的抽象基類,可以被分布式文件系統繼承,所有可能使用 Hadoop文件系統的代碼,都要使用這個類。
Hadoop為FileSystem這個抽象類提供了多種具體實現。
DistributedFileSystem就是FileSystem在HDFS文件系統中的具體實現。
FileSystem的open()方法返回的是一個輸入流FSDataInputStream對象,在HDFS文件系統中 ,具體的輸入流就是DFSInputStream;FileSystem中的create()方法返回的是一個輸出流 FSDataOutputStream對象,在HDFS文件系統中,具體的輸出流就是DFSOutputStream。
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
FSDataInputStream in = fs.open(new Path(uri));
FSDataOutputStream out = fs.create(new Path(uri));
備注:創建一個Configuration對象時,其構造方法會默認加載工程項目下兩個配置文件,分別是 hdfs-site.xml以及core-site.xml,這兩個文件中會有訪問HDFS所需的參數值,主要是 fs.defaultFS,指定了HDFS的地址(比如hdfs://localhost:9000),有了這個地址客戶端就可以 通過這個地址訪問HDFS了。
讀取文件
import java.io.BufferedReader;
import java.io.InputStreamReader ;
import org.apache.hadoop.conf.Configuration ;
import org.apache.hadoop.fs.FileSystem ;
import org.apache.hadoop.fs.Path ;
import org.apache.hadoop.fs.FSDataInputStream ;
public class Chapter3 {
public static void main(String[] args) {
try {
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
Path filename = new Path(“hdfs://localhost:9000/user/hadoop/test.txt");
FSDataInputStream is = fs.open(filename);
BufferedReader d = new BufferedReader(new InputStreamReader(is));
String content = d.readLine(); //讀取文件一行
System.out.println(content);
d.close(); //關閉文件
fs.close(); //關閉hdfs
} catch (Exception e) {
e.printStackTrace();
}
}
}
寫入文件
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.Path;
public class Chapter3 {
public static void main(String[] args) {
try {
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
byte[] buff = "Hello world".getBytes(); // 要寫入的內容
String filename = " hdfs://localhost:9000/user/hadoop/test.txt "; //要寫入的文件名
FSDataOutputStream os = fs.create(new Path(filename));
os.write(buff,0,buff.length);
System.out.println("Create:"+ filename);
} catch (Exception e) {
e.printStackTrace();
}
}
}
7、HDFS編程實踐
首先啟動hadoop
$ cd /usr/local/hadoop
$ ./bin/hdfs namenode -format # 格式化hdfs文件系統,初始化時使用,之前執行后就不需再執行
$ ./bin/start-dfs.sh
常用命令
HDFS有很多shell命令,其中,fs命令可以說是HDFS最常用的命令。利用該命令可以 查看HDFS文件系統的目錄結構、上傳和下載數據、創建文件等。
該命令的用法為: hadoop fs [genericOptions] [commandOptions]
備注:Hadoop中有三種Shell命令方式:
-
hadoop fs適用於任何不同的文件系統,比如本地文件系統和HDFS文件系統。
-
hadoop dfs只能適用於HDFS文件系統。
-
hdfs dfs跟hadoop dfs的命令作用一樣,也只能適用於HDFS文件系統。
實例
hadoop fs -ls
hadoop fs -mkdir
例中“./”表示“/usr/local/hadoop/bin”路徑。
hadoop fs -cat
hadoop fs -copyFromLocal
WEB管理界面
http://ip:50070,默認端口50070
利用Java API與HDFS進行交互
maven項目中引入
<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-common -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.6.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-hdfs -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>2.6.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-client -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>2.6.0</version>
</dependency>
</dependencies>
寫一個FileSystem獲取工具類:
package com.yl.hdfs;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
/**
* 單例模式生成FileSystem
*
* @author guilin
*
*/
public class FileSystemFactory {
private static class FileSystemFactoryHolder{
public static FileSystem instance;
static {
Configuration conf = new Configuration();
conf.set("fs.defaultFS","hdfs://172.20.10.6:9000");
conf.set("fs.hdfs.impl", "org.apache.hadoop.hdfs.DistributedFileSystem");
try {
instance = FileSystem.get(conf);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static FileSystem getInsatnce() {
return FileSystemFactoryHolder.instance;
}
}
實例:利用hadoop 的java api檢測偽分布式文件系統HDFS上是否存在某個文件?
其中172.20.10.6是我hadoop機器上的ip地址。
package com.yl.hdfs;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class HdfsExists {
public static void main(String[] args) {
try {
String filename = "/user/hadoop/input";
FileSystem fs = FileSystemFactory.getInsatnce();
if(fs.exists(new Path(filename))){
System.out.println("文件存在");
}else{
System.out.println("文件不存在");
}
fs.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
驗證一下是否存在:
實例:寫HDFS上的文件?
package com.yl.hdfs;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class HdfsWrite {
public static void main(String[] args) {
try {
FileSystem fs = FileSystemFactory.getInsatnce();
byte[] buff = "Hello world!".getBytes(); // 要寫入的內容
String filename = "/user/22113/test"; //要寫入的文件名
FSDataOutputStream os = fs.create(new Path(filename));
os.write(buff,0,buff.length);
System.out.println("Create:"+ filename);
os.close();
fs.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
實例:讀HDFS上的文件?
package com.yl.hdfs;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class HdfsRead {
public static void main(String[] args) {
try {
FileSystem fs = FileSystemFactory.getInsatnce();
String filename = "/user/22113/test.txt"; //要讀的文件名
FSDataInputStream in = fs.open(new Path(filename));
BufferedReader bis = new BufferedReader(new InputStreamReader(in));
System.out.println(bis.readLine());
bis.close();
fs.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
常見錯誤:
- java.net.ConnectException
Connection refused: no further information
此例環境:windows中安裝虛擬機運行Hadoop。由於hadoop中core-site.xml中設置的fs.defaultFS是hdfs://localhost:9000,所以報錯,應該將lcoalhost替換成自己虛擬機分配的ip地址,之后重啟hadoop。
-
記得開啟對應的虛擬機端口,端口未開啟會報錯。《CentOS7 中開放端口》
-
org.apache.hadoop.security.AccessControlException
Permission denied: user=22113, access=WRITE, inode="/user/hadoop":hadoop:supergroup:drwxr-xr-x
沒有寫入權限,應該設置該文件夾權限。
文件權限由讀、可執行變成讀、寫、可執行。現在/user/22113文件夾皆可以寫入內容了。
- org.apache.hadoop.ipc.RemoteException(java.io.IOException)
File /user/22113/test.txt could only be replicated to 0 nodes instead of minReplication (=1). There are 1 datanode(s) running and 1 node(s) are excluded in this operation.
這個錯誤從網上找了很久,都沒解決。有人說是DataNode沒啟動,但是我用jps命令查看,發現DataNode是在運行。還有人說是format多次NameNode與DataNode導致的,可是這都不是原因。后來突然想起關閉虛擬機防火牆,發現就可以了,功能正常運作,具體原因待分析。
結尾
本文是根據中國大學MOOC網站上,課程《大數據技術原理與應用》的課件ppt撰寫的一篇博文。由於自己也是正在跟着這門課進行學習,所以很多專業性知識點都是截取課件ppt上的內容。順便推薦一下這門課程,老師講解的知識點非常細致,還有對操作步驟詳細記錄的博客資源。
感謝廈門大學數據庫實驗室,感謝林子雨老師提供的這么優秀的資源。