你想了解的分散式檔案系統HDFS,看這一篇就夠了
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上的內容。順便推薦一下這門課程,老師講解的知識點非常細緻,還有對操作步驟詳細記錄的部落格資源。
感謝廈門大學資料庫實驗室,感謝林子雨老師提供的這麼優秀的資源。