目錄
(一)概述
在海量數據的業務場景中,我們每天採集的數據通常是GB級別的,系統所存儲的數據通常是TB級別的,甚至是PB級別。爲了應爲數據存儲的管理和擴展問題,大數據集羣通常採用橫向擴展的方式來滿足數據增長的需求,即以網絡互連的節點爲單位擴大存儲容量。爲了構建橫向擴展的分佈式文件系統,通常需要解決如下幾個問題:
1.數據能備份:由於橫向擴展的集羣節點通常採用廉價的服務器,因而出現故障的機率較大,需要分佈式文件系統能夠很好的應對各種問題,也就是有良好的容錯性;
2.存儲大文件:在分佈式文件系統中,存儲GB級別的文件很常見,也可能存儲大量的KB級小文件,這與傳統文件系統的場景很不同,要求大數據集羣在IO方面有良好的性能;
3.一次寫入多次讀取:大多數場景下,文件是追加寫入的,且寫入完成後不會隨機修改,只會讀取。因此,文件的追加操作需要重點保障讀寫性能和原子操作。
在2003年,Google發佈了分佈式文件系統GFS的相關論文,在此基礎上,Apache開源了頂級項目:Hadoop,而HDFS正是Hadoop中的分佈式文件系統,也是GFS的開源實現。
(二)文件系統的應用
構建分佈式文件系統,通常有兩種方式:文件級別和塊級別。
這裏首先介紹文件級別,通常採用的方案是基於現有文件系統的主從架構:Master/Slave,也就是給定N個網絡節點,每個節點都裝有Linux操作系統,選出一個節點作爲Master,記錄文件的元信息,其他節點作爲Slave,存儲實際文件。
該方案是初級的分佈式文件系統實現,但存在如下兩個難以解決的問題:
1.難以負載均衡:文件級別的分佈式文件系統,以文件爲單位進行存儲,由於用戶的文件大小不統一,因此難以保證每個節點的數據都是均衡的;
2.難以並行處理:如果以文件爲單位進行存儲,當多個分佈在不同節點上的並行任務讀取同一文件時,存儲該文件的節點網絡帶寬便會成爲瓶頸,從而制約上層框架的並行處理效率。
文件級別的分佈式文件系統結構圖如下:
爲了解決文件級別的分佈式文件系統存在的問題,我們提出了塊級別的分佈式文件系統,其核心思想是將文件分成等大的數據塊,例如HDFS默認是128MB,並以數據塊爲單位存儲到不同的節點上,進而解決負載均衡和並行處理的問題。如下圖所示:
(三)HDFS基本架構
HDFS是典型的塊級別分佈式文件系統,主節點被稱爲Namenode,負責管理元信息和子節點,子節點被稱爲Datanode,負責存儲實際的數據塊,如下圖所示:
Namenode是HDFS的管理者,主要包括如下功能:
1.管理元信息:Namenode以目錄樹的形式維護整個文件系統目錄、文件的數據塊等信息;
2.管理Datanode:Datanode需要週期性的向Namenode彙報心跳信息,一旦Namenode發現Datanode出現了問題,會在其他存活的Datanode節點上重構丟失的數據塊;
3.HA高可用性:爲了防止Namenode出現問題之後集羣不可用,通常會啓動一個備用的Namenode,實現HA的高可用性;
4.狀態同步:主從Namenode並不是通過強一致的協議保證狀態一致的,而是通過第三方的文件共享存儲系統,主Namenode將EditLog日誌寫入共享存儲系統,從Namenode讀取這些日誌並執行修改操作。
Datanode主要是用來對文件數據塊的讀寫。存儲數據塊和塊的校驗,因爲數據在網絡的傳輸中可能存在數據丟失的問題,所以需要校驗。同時,DataNode要保持與NameNode的通信,一般每3秒發送一次心跳包。一般來說,數據是以block存儲在DataNode節點上,在DataNode節點上提交文件,那麼第一個塊就是存儲在這個節點上(選擇最近的一個),如果不是在DataNode節點上提交的,就隨即挑選一個磁盤合適CPU負荷的節點。第二個block存儲在不同機架上的一個節點,假設副本數爲3,那麼第三個block存儲在與第二個block相同機架上的不同節點。
(四)HDFS關鍵技術
HDFS在實現時採用了大量分佈式技術,典型的有如下幾種:
1.容錯性設計
HDFS有良好的容錯性設計,以降低節點故障情況下數據丟失的可能性。當Namenode故障時,會導致整個文件系統數據不可用,因此HDFS會爲每個Active Namenode分配一個Standby Namenode,用於故障時備用;當Datanode故障時,由於數據塊的多副本策略,Namenode可以在其他節點上重構故障Datanode上的數據塊;當數據塊損壞時,也就是校驗碼不一致時,Namenode會通過其他節點上的正常副本來重構首受損的數據塊。
2.副本放置策略
數據塊副本放置策略決定了每個數據塊多個副本存放節點的選擇,在保證讀寫性能的前提下,儘可能的提高數據的可靠性。副本放置策略與集羣物理拓撲結構直接相關,通常情況下,一個集羣由多個機架構成,每個機架由16~64個物理節點組成,機架內部的節點時通過內部交換機通信的,機架之間的節點時通過外部節點通信的。由於機架間的節點通信需要多層交換機,相比於機架內節點通信,讀寫延遲要高一些。相同機架內部的節點通常是綁定在一起的,多種資源可能是共享的,例如插座、交換機等,因此同時不可用的概率要比不同機架節點高很多。
考慮到集羣物理拓撲結構特點,HDFS默認採用三副本放置策略。當客戶端與Datanode同節點時,上層計算框架處理HDFS數據時,每個任務實際上就是一個客戶端,運行在與Datanode相同的計算節點上,三副本放置策略爲:第一個副本寫到同Datanode上,另外兩個副本寫到相同機架的不同Datanode上。當客戶端與Datanode不同節點時,也就是HDFS之外的應用程序會向HDFS寫數據,例如Flink Sink,HDFS會隨機選擇一個Datanode作爲第一個副本放置節點,其他兩個副本寫到另一個相同機架不同的Datanode上。
3.異構存儲介質
隨着HDFS的不斷完善,已經從最初的單存儲介質(磁盤)的單一文件系統,演化爲支持異構存儲介質的綜合性分佈式文件系統,使得HDFS能夠很好的利用新型存儲介質,使得HDFS變成了一個提供混合存儲方式的存儲系統,用戶能夠根據自己的業務特點,選擇不同的存儲介質。HDFS目前支持的存儲介質主要包括:
Disk:默認的磁盤介質;
Archive:高存儲密度但耗電較少,通常用來存儲冷數據;
SSD:固態硬盤,新型存儲介質,速度較快;
Ram Disk:數據首先被寫道內存中,同時向該存儲介質中再異步寫一份。
4.集中式緩存管理
HDFS允許用戶將一部分目錄或文件緩存在off-heap內存中,以加速對這些數據的訪問效率,該機制被稱爲集中式緩存管理,包括如下幾方面的有點:
提高集羣的內存利用效率:當使用操作系統的緩存時,對一個數據塊的重讀讀會導致所有的副本都會被放到緩衝區中,造成內存浪費,使用集中式緩存,用戶可以指定n個副本的m個被緩存,節約n-m的內存;
防止被頻繁使用的數據從內存中清除:當使用操作系統緩存時,操作系統使用自帶的內存置換算法管理內存,此時容易讓熱數據不斷寫入內存,之後從內存中清除,導致數據訪問速度的不穩定;
提高讀取效率:Datanode緩存統一由Namenode來管理,上層計算框架的調度器查詢數據塊的緩存列表,並通過一定的調度策略將任務儘可能調度到緩存塊所在節點上,以提高數據讀性能;當數據塊被Datanode緩存後,HDFS會使用一個高效的、支持zero-copy的新API加快讀速度,客戶端的開銷基本是0。
(五)HDFS訪問方式
HDFS提供了兩類shell命令:用戶命令和管理員命令,具體如下:
1.用戶命令
HDFS提供了大量用戶命令,常用的有文件操作命令dfs,文件一致性檢查命令fsck和分佈式文件複製命令distcp,具體如下:
(1)文件操作命令dfs
文件操作命令是文件系統交互的命令,可以是HDFS,也可以是Hadoop支持的文件系統,語法如下:
$HADOOP_HOME/bin/hadoop fs <args>
所有命令均會接收文件uri作爲參數,如果直接操作HDFS上的文件,也可以使用如下命令:
$HADOOP_HOME/bin/hdfs dfs <args>
除了執行命令的前綴不同,HDFS大部分文件操作命令與Linux自帶命令類似,例如mkdir、rm、mv等。
(2)文件一致性檢查命令fsck
fsck命令的用法和參數如下:
(3)分佈式文件複製命令distcp
分佈式文件複製命令distcp主要功能包括集羣內文件並行複製和集羣間文件並行複製,例如將/user/gai/從集羣nm1複製到集羣nm2上,加入兩個集羣的版本相同,那麼命令如下:
bin/hadoop/ distcp hdfs://nm1:8020/user/gai hdfs://nm2:8020/user/gai
2.管理員命令
管理員命令主要是針對服務生命週期管理的,比如啓動/關閉Namenode/Datanode,HDFS份額管理等,例如啓動和關閉Namenode:
sbin/hadoop-daemon.sh start namenode
sbin/hadoop-daemon.sh stop namenode
(六)HDFS Java API
例子如下:
/**
* hdfs入門程序:
* 面向對象:一切皆對象
* 文件系統也可以看做是一類事物、FileSystem
*
* 一定有共同的行爲和屬性:
* 1.屬性--就是--URL
* 本地文件系統的URL: file:///c:myProgram
* HDFS文件系統的URL: fs.defaultFS=hdfs://hadoop02:9000
* 2.行爲/方法--就是--上傳和下載
*
* FileSystem類的相關方法:
* .get()----->靜態方法,用來獲取FileSystem類的這個實例對象的,而不是 做下載的,此方法最少傳一個參數否則要傳三個參數
*/
public class HelloHDFS {
/**
* 從windows上傳和下載到HDFS
*/
public static void main(String[] args) throws Exception {
/**
* 插曲:創建對象的方式有五種:
* 1.構造方法(一般用這種)
* 2.靜態方法(一般用這種)
* 3.反射
* 4.克隆
* 5.反序列化
*/
//Configuration是配置對象,conf可以理解爲包含了所有配置信息的一個集合,可以認爲是Map
//在執行這行代碼的時候底層會加載一堆配置文件 core-default.xml;hdfs-default.xml;mapred-default.xml;yarn-default.xml
Configuration conf = new Configuration();
//相當於通過配置文件的key獲取到value的值
conf.set("fs.defaultFS","hdfs://hadoop02:9000");
/**
* 更改操作用戶有兩種方式:(系統會自動識別我們的操作用戶,如果我們設置,將會報錯會拒絕Administrator用戶(windows用戶))
* 1.直接設置運行環境中的用戶名爲hadoop,此方法不方便因爲打成jar包執行還是需要改用戶,右鍵Run As--Run Configurations--Arguments--VM arguments--輸入-DHADOOP_USER_NAME=hadoop
* 2.直接在代碼中進行聲明
*/
//更改操作用戶爲hadoop
System.setProperty("HADOOP_USER_NAME","hadoop");
//獲取文件系統對象(目的是獲取HDFS文件系統)
FileSystem fs=FileSystem.get(conf);
//直接輸出fs對象是org.apache.hadoop.fs.LocalFileSystem@70e8f8e
//這說明是本地文件系統對象。代碼在eclipse所嵌入的jvm中執行的,jvm是安裝在Windows下的,所以是windows文件系統對象
//所以要返回來指定HDFS
System.out.println(fs);
//上傳的API
fs.copyFromLocalFile(new Path("c:/ss.txt"), new Path("/a"));
//下載的API 不改名就不用寫文件名字也行
fs.copyToLocalFile(new Path("/a/qqq.txt"), new Path("c:/qqq.txt"));
fs.close();
/**
* .crc 是校驗文件
* 每個塊的元數據信息都只會記錄合法的數據起始偏移量。
* 如果進行了非法的數據追加,最終是能夠下載正確的數據的。
* 如果在數據的中間更改了數據,造成了採用CRC算法計算出來的校驗值和最初存入HDFS的校驗值不一致。HDFS就認爲當前這個文件被損壞了。
*/
}
}