在現代的企業環境中,單機容量每每沒法存儲大量數據,須要跨機器存儲。統一管理分佈在集羣上的文件系統稱爲分佈式文件系統。而一旦在系統中,引入網絡,就不可避免地引入了全部網絡編程的複雜性,例如挑戰之一是若是保證在節點不可用的時候數據不丟失。java
傳統的網絡文件系統(NFS)雖然也稱爲分佈式文件系統,可是其存在一些限制。因爲NFS中,文件是存儲在單機上,所以沒法提供可靠性保證,當不少客戶端同時訪問NFS Server時,很容易形成服務器壓力,形成性能瓶頸。另外若是要對NFS中的文件進行操做,須要首先同步到本地,這些修改在同步到服務端以前,其餘客戶端是不可見的。某種程度上,NFS不是一種典型的分佈式系統,雖然它的文件的確放在遠端(單一)的服務器上面。node
從NFS的協議棧能夠看到,它事實上是一種VFS(操做系統對文件的一種抽象)實現。web
HDFS,是Hadoop Distributed File System的簡稱,是Hadoop抽象文件系統的一種實現。Hadoop抽象文件系統能夠與本地系統、Amazon S3等集成,甚至能夠經過Web協議(webhsfs)來操做。HDFS的文件分佈在集羣機器上,同時提供副本進行容錯及可靠性保證。例如客戶端寫入讀取文件的直接操做都是分佈在集羣各個機器上的,沒有單點性能壓力。正則表達式
若是你從零開始搭建一個完整的集羣,參考[Hadoop集羣搭建詳細步驟(2.6.0)](http://blog.csdn.net/bingduanlbd/article/details/51892750)shell
HDFS設計之初就很是明確其應用場景,適用與什麼類型的應用,不適用什麼應用,有一個相對明確的指導原則。apache
存儲很是大的文件:這裏很是大指的是幾百M、G、或者TB級別。實際應用中已有不少集羣存儲的數據達到PB級別。根據Hadoop官網,Yahoo!的Hadoop集羣約有10萬顆CPU,運行在4萬個機器節點上。更多世界上的Hadoop集羣使用狀況,參考Hadoop官網.編程
採用流式的數據訪問方式: HDFS基於這樣的一個假設:最有效的數據處理模式是一次寫入、屢次讀取數據集常常從數據源生成或者拷貝一次,而後在其上作不少分析工做
分析工做常常讀取其中的大部分數據,即便不是所有。 所以讀取整個數據集所需時間比讀取第一條記錄的延時更重要。數組
有些場景不適合使用HDFS來存儲數據。下面列舉幾個:緩存
1) 低延時的數據訪問
對延時要求在毫秒級別的應用,不適合採用HDFS。HDFS是爲高吞吐數據傳輸設計的,所以可能犧牲延時HBase更適合低延時的數據訪問。安全
2)大量小文件
文件的元數據(如目錄結構,文件block的節點列表,block-node mapping)保存在NameNode的內存中, 整個文件系統的文件數量會受限於NameNode的內存大小。
經驗而言,一個文件/目錄/文件塊通常佔有150字節的元數據內存空間。若是有100萬個文件,每一個文件佔用1個文件塊,則須要大約300M的內存。所以十億級別的文件數量在現有商用機器上難以支持。
3)多方讀寫,須要任意的文件修改
HDFS採用追加(append-only)的方式寫入數據。不支持文件任意offset的修改。不支持多個寫入器(writer)。
物理磁盤中有塊的概念,磁盤的物理Block是磁盤操做最小的單元,讀寫操做均以Block爲最小單元,通常爲512 Byte。文件系統在物理Block之上抽象了另外一層概念,文件系統Block物理磁盤Block的整數倍。一般爲幾KB。Hadoop提供的df、fsck這類運維工具都是在文件系統的Block級別上進行操做。
HDFS的Block塊比通常單機文件系統大得多,默認爲128M。HDFS的文件被拆分紅block-sized的chunk,chunk做爲獨立單元存儲。比Block小的文件不會佔用整個Block,只會佔據實際大小。例如, 若是一個文件大小爲1M,則在HDFS中只會佔用1M的空間,而不是128M。
HDFS的Block爲何這麼大?
是爲了最小化查找(seek)時間,控制定位文件與傳輸文件所用的時間比例。假設定位到Block所需的時間爲10ms,磁盤傳輸速度爲100M/s。若是要將定位到Block所用時間佔傳輸時間的比例控制1%,則Block大小須要約100M。
可是若是Block設置過大,在MapReduce任務中,Map或者Reduce任務的個數 若是小於集羣機器數量,會使得做業運行效率很低。
Block抽象的好處
block的拆分使得單個文件大小能夠大於整個磁盤的容量,構成文件的Block能夠分佈在整個集羣, 理論上,單個文件能夠佔據集羣中全部機器的磁盤。
Block的抽象也簡化了存儲系統,對於Block,無需關注其權限,全部者等內容(這些內容都在文件級別上進行控制)。
Block做爲容錯和高可用機制中的副本單元,即以Block爲單位進行復制。
整個HDFS集羣由Namenode和Datanode構成master-worker(主從)模式。Namenode負責構建命名空間,管理文件的元數據等,而Datanode負責實際存儲數據,負責讀寫工做。
Namenode存放文件系統樹及全部文件、目錄的元數據。元數據持久化爲2種形式:
可是持久化數據中不包括Block所在的節點列表,及文件的Block分佈在集羣中的哪些節點上,這些信息是在系統重啓的時候從新構建(經過Datanode彙報的Block信息)。
在HDFS中,Namenode可能成爲集羣的單點故障,Namenode不可用時,整個文件系統是不可用的。HDFS針對單點故障提供了2種解決機制:
1)備份持久化元數據
將文件系統的元數據同時寫到多個文件系統, 例如同時將元數據寫到本地文件系統及NFS。這些備份操做都是同步的、原子的。
2)Secondary Namenode
Secondary節點按期合併主Namenode的namespace image和edit log, 避免edit log過大,經過建立檢查點checkpoint來合併。它會維護一個合併後的namespace image副本, 可用於在Namenode徹底崩潰時恢復數據。下圖爲Secondary Namenode的管理界面:
Secondary Namenode一般運行在另外一臺機器,由於合併操做須要耗費大量的CPU和內存。其數據落後於Namenode,所以當Namenode徹底崩潰時,會出現數據丟失。 一般作法是拷貝NFS中的備份元數據到Second,將其做爲新的主Namenode。
在HA(High Availability高可用性)中能夠運行一個Hot Standby,做爲熱備份,在Active Namenode故障以後,替代原有Namenode成爲Active Namenode。
數據節點負責存儲和提取Block,讀寫請求可能來自namenode,也可能直接來自客戶端。數據節點週期性向Namenode彙報本身節點上所存儲的Block相關信息。
DataNode一般直接從磁盤讀取數據,可是頻繁使用的Block能夠在內存中緩存。默認狀況下,一個Block只有一個數據節點會緩存。可是能夠針對每一個文件能夠個性化配置。
做業調度器能夠利用緩存提高性能,例如MapReduce能夠把任務運行在有Block緩存的節點上。
用戶或者應用能夠向NameNode發送緩存指令(緩存哪一個文件,緩存多久), 緩存池的概念用於管理一組緩存的權限和資源。
咱們知道NameNode的內存會制約文件數量,HDFS Federation提供了一種橫向擴展NameNode的方式。在Federation模式中,每一個NameNode管理命名空間的一部分,例如一個NameNode管理/user目錄下的文件, 另外一個NameNode管理/share目錄下的文件。
每一個NameNode管理一個namespace volumn,全部volumn構成文件系統的元數據。每一個NameNode同時維護一個Block Pool,保存Block的節點映射等信息。各NameNode之間是獨立的,一個節點的失敗不會致使其餘節點管理的文件不可用。
客戶端使用mount table將文件路徑映射到NameNode。mount table是在Namenode羣組之上封裝了一層,這一層也是一個Hadoop文件系統的實現,經過viewfs:協議訪問。
在HDFS集羣中,NameNode依然是單點故障(SPOF: Single Point Of Failure)。元數據同時寫到多個文件系統以及Second NameNode按期checkpoint有利於保護數據丟失,可是並不能提升可用性。
這是由於NameNode是惟一一個對文件元數據和file-block映射負責的地方, 當它掛了以後,包括MapReduce在內的做業都沒法進行讀寫。
當NameNode故障時,常規的作法是使用元數據備份從新啓動一個NameNode。元數據備份可能來源於:
啓動新的Namenode以後,須要從新配置客戶端和DataNode的NameNode信息。另外重啓耗時通常比較久,稍具規模的集羣重啓常常須要幾十分鐘甚至數小時,形成重啓耗時的緣由大體有:
1) 元數據鏡像文件載入到內存耗時較長。
2) 須要重放edit log
3) 須要收到來自DataNode的狀態報告而且知足條件後才能離開安全模式提供寫服務。
採用HA的HDFS集羣配置兩個NameNode,分別處於Active和Standby狀態。當Active NameNode故障以後,Standby接過責任繼續提供服務,用戶沒有明顯的中斷感受。通常耗時在幾十秒到數分鐘。
HA涉及到的主要實現邏輯有
1) 主備需共享edit log存儲。
主NameNode和待命的NameNode共享一份edit log,當主備切換時,Standby經過回放edit log同步數據。
共享存儲一般有2種選擇
QJM是專門爲HDFS的HA實現而設計的,用來提供高可用的edit log。QJM運行一組journal node,edit log必須寫到大部分的journal nodes。一般使用3個節點,所以容許一個節點失敗,相似ZooKeeper。注意QJM沒有使用ZK,雖然HDFS HA的確使用了ZK來選舉主Namenode。通常推薦使用QJM。
2)DataNode須要同時往主備發送Block Report
由於Block映射數據存儲在內存中(不是在磁盤上),爲了在Active NameNode掛掉以後,新的NameNode可以快速啓動,不須要等待來自Datanode的Block Report,DataNode須要同時向主備兩個NameNode發送Block Report。
3)客戶端須要配置failover模式(失效備援模式,對用戶透明)
Namenode的切換對客戶端來講是無感知的,經過客戶端庫來實現。客戶端在配置文件中使用的HDFS URI是邏輯路徑,映射到一對Namenode地址。客戶端會不斷嘗試每個Namenode地址直到成功。
4)Standby替代Secondary NameNode
若是沒有啓用HA,HDFS獨立運行一個守護進程做爲Secondary Namenode。按期checkpoint,合併鏡像文件和edit日誌。
若是當主Namenode失敗時,備份Namenode正在關機(中止 Standby),運維人員依然能夠從頭啓動備份Namenode,這樣比沒有HA的時候更省事,算是一種改進,由於重啓整個過程已經標準化到Hadoop內部,無需運維進行復雜的切換操做。
NameNode的切換經過代failover controller來實現。failover controller有多種實現,默認實現使用ZooKeeper來保證只有一個Namenode處於active狀態。
每一個Namenode運行一個輕量級的failover controller進程,該進程使用簡單的心跳機制來監控Namenode的存活狀態並在Namenode失敗時觸發failover。Failover能夠由運維手動觸發,例如在平常維護中須要切換主Namenode,這種狀況graceful(優雅的) failover,非手動觸發的failover稱爲ungraceful failover。
在ungraceful failover的狀況下,沒有辦法肯定失敗(被斷定爲失敗)的節點是否中止運行,也就是說觸發failover後,以前的主Namenode可能還在運行。QJM一次只容許一個Namenode寫edit log,可是以前的主Namenode仍然能夠接受讀請求。Hadoop使用fencing來殺掉以前的Namenode。Fencing經過收回以前Namenode對共享的edit log的訪問權限、關閉其網絡端口使得原有的Namenode不能再繼續接受服務請求。使用STONITH技術也能夠將以前的主Namenode關機。
最後,HA方案中Namenode的切換對客戶端來講是不可見的,前面已經介紹過,主要經過客戶端庫來完成。
HDFS提供了各類交互方式,例如經過Java API、HTTP、shell命令行的。命令行的交互主要經過hadoop fs來操做。例如:
Hadoop中,文件和目錄的權限相似於POSIX模型,包括讀、寫、執行3種權限:
每一個文件或目錄都有owner,group,mode三個屬性,owner指文件的全部者,group爲權限組。mode
由全部者權限、文件所屬的組中組員的權限、非全部者非組員的權限組成。下圖表示其全部者root擁有讀寫權限,supergroup組的組員有讀權限,其餘人有讀權限。
文件權限是否開啓經過dfs.permissions.enabled屬性來控制,這個屬性默認爲false,沒有打開安全限制,所以不會對客戶端作受權校驗,若是開啓安全限制,會對操做文件的用戶作權限校驗。特殊用戶superuser是Namenode進程的標識,不會針對該用戶作權限校驗。
最後看一下ls命令的執行結果:
這個返回結果相似於Unix系統下的ls命令,第一欄爲文件的mode,d表示目錄,緊接着3種權限9位。 第二欄是指文件的副本數,這個數量經過dfs.replication
配置,目錄則使用-表示沒有副本一說。其餘諸如全部者、組、更新時間、文件大小跟Unix系統中的ls命令一致。
若是須要查看集羣狀態或者瀏覽文件目錄,能夠訪問Namenode暴露的Http Server查看集羣信息,通常在namenode所在機器的50070端口。
前面Hadoop的文件系統概念是抽象的,HDFS只是其中的一種實現。Hadoop提供的實現以下圖:
簡單介紹一下,Local是對本地文件系統的抽象,hdfs就是咱們最多見的,兩種web形式(webhdfs,swebhdfs)的實現經過HTTP提供文件操做接口。har是Hadoop體系下的壓縮文件,當文件不少的時候能夠壓縮成一個大文件,能夠有效減小元數據的數量。viewfs就是咱們前面介紹HDFS Federation張提到的,用來在客戶端屏蔽多個Namenode的底層細節。ftp顧名思義,就是使用ftp協議來實現,對文件的操做轉化爲ftp協議。s3a是對Amazon雲服務提供的存儲系統的實現,azure則是微軟的雲服務平臺實現。
前面咱們提到了使用命令行跟HDFS交互,事實上還有不少方式來操做文件系統。例如Java應用程序可使用org.apache.hadoop.fs.FileSystem來操做,其餘形式的操做也都是基於FileSystem進行封裝。咱們這裏主要介紹一下HTTP的交互方式。
WebHDFS和SWebHDFS協議將文件系統暴露HTTP操做,這種交互方式比原生的Java客戶端慢,不適合操做大文件。經過HTTP,有2種訪問方式,直接訪問和經過代理訪問
直接訪問
直接訪問的示意圖以下:
Namenode和Datanode默認打開了嵌入式web server,即dfs.webhdfs.enabled默認爲true。webhdfs經過這些服務器來交互。元數據的操做經過namenode完成,文件的讀寫首先發到namenode,而後重定向到datanode讀取(寫入)實際的數據流。
經過HDFS代理
採用代理的示意圖如上所示。 使用代理的好處是能夠經過代理實現負載均衡或者對帶寬進行限制,或者防火牆設置。代理經過HTTP或者HTTPS暴露爲WebHDFS,對應爲webhdfs和swebhdfs URL Schema。
代理做爲獨立的守護進程,獨立於namenode和datanode,使用httpfs.sh腳本,默認運行在14000端口
除了FileSystem直接操做,命令行,HTTTP外,還有C語言API,NFS,FUSER等方式,這裏不作過多介紹。
實際的應用中,對HDFS的大多數操做仍是經過FileSystem來操做,這部分重點介紹一下相關的接口,主要關注HDFS的實現類DistributedFileSystem及相關類。
可使用URL來讀取數據,或者直接使用FileSystem操做。
java.net.URL類提供了資源定位的統一抽象,任何人均可以本身定義一種URL Schema,並提供相應的處理類來進行實際的操做。hdfs schema即是這樣的一種實現。
爲了使用自定義的Schema,須要設置URLStreamHandlerFactory,這個操做一個JVM只能進行一次,屢次操做會致使不可用,一般在靜態塊中完成。下面的截圖是一個使用示例:
1) 首先獲取FileSystem實例,通常使用靜態get工廠方法
若是是本地文件,經過getLocal獲取本地文件系統對象:
public static LocalFileSystem getLocal(COnfiguration conf) thrown IOException
2)調用FileSystem的open方法獲取一個輸入流:
默認狀況下,open使用4KB的Buffer,能夠根據須要自行設置。
3)使用FSDataInputStream進行數據操做
FSDataInputStream是java.io.DataInputStream的特殊實現,在其基礎上增長了隨機讀取、部分讀取的能力
隨機讀取操做經過Seekable接口定義:
seek操做開銷昂貴,慎用。
部分讀取經過PositionedReadable接口定義:
在HDFS中,文件使用FileSystem類的create方法及其重載形式來建立,create方法返回一個輸出流FSDataOutputStream,能夠調用返回輸出流的getPos方法查看當前文件的位移,可是不能進行seek操做,HDFS僅支持追加操做。
建立時,能夠傳遞一個回調接口Peofressable,獲取進度信息
append(Path f)方法用於追加內容到已有文件,可是並非全部的實現都提供該方法,例如Amazon的文件實現就沒有提供追加功能。
下面是一個例子:
使用mkdirs()方法,會自動建立沒有的上級目錄
HDFS中元數據封裝在FileStatus類中,包括長度、block size,replicaions,修改時間、全部者、權限等信息。使用FileSystem提供的getFileStatus方法獲取FileStatus。exists()方法判斷文件或者目錄是否存在;
列出文件(list),則使用listStatus方法,能夠查看文件或者目錄的信息
Path是個文件的時候,返回長度爲1的數組。FileUtil提供的stat2Paths方法用於將FileStatus轉化爲Path對象。
globStatus則使用通配符對文件路徑進行匹配:
public FileStatus[] globStatus(Path pathPattern) throws IOException
PathFilter用於自定義文件名過濾,不能根據文件屬性進行過濾,相似於java.io.FileFilter。例以下面這個例子排除到給定正則表達式的文件:
使用FileSystem的delete()方法
public boolean delete(Path f , boolean recursive) throws IOException;
recursive參數在f是個文件的時候被忽略。若是f是文件而且recursice爲true,則刪除整個目錄,不然拋出異常.
接下來詳細介紹HDFS讀寫數據的流程,以及一致性模型相關的一些概念。
大體讀文件的流程以下:
1)客戶端傳遞一個文件Path給FileSystem的open方法
2)DFS採用RPC遠程獲取文件最開始的幾個block的datanode地址。Namenode會根據網絡拓撲結構決定返回哪些節點(前提是節點有block副本),若是客戶端自己是Datanode而且節點上恰好有block副本,直接從本地讀取。
3)客戶端使用open方法返回的FSDataInputStream對象讀取數據(調用read方法)
4)DFSInputStream(FSDataInputStream實現了改類)鏈接持有第一個block的、最近的節點,反覆調用read方法讀取數據
5)第一個block讀取完畢以後,尋找下一個block的最佳datanode,讀取數據。若是有必要,DFSInputStream會聯繫Namenode獲取下一批Block 的節點信息(存放於內存,不持久化),這些尋址過程對客戶端都是不可見的。
6)數據讀取完畢,客戶端調用close方法關閉流對象
在讀數據過程當中,若是與Datanode的通訊發生錯誤,DFSInputStream對象會嘗試從下一個最佳節點讀取數據,而且記住該失敗節點, 後續Block的讀取不會再鏈接該節點
讀取一個Block以後,DFSInputStram會進行檢驗和驗證,若是Block損壞,嘗試從其餘節點讀取數據,而且將損壞的block彙報給Namenode。
客戶端鏈接哪一個datanode獲取數據,是由namenode來指導的,這樣能夠支持大量併發的客戶端請求,namenode儘量將流量均勻分佈到整個集羣。
Block的位置信息是存儲在namenode的內存中,所以相應位置請求很是高效,不會成爲瓶頸。
步驟分解
1)客戶端調用DistributedFileSystem的create方法
2)DistributedFileSystem遠程RPC調用Namenode在文件系統的命名空間中建立一個新文件,此時該文件沒有關聯到任何block。 這個過程當中,Namenode會作不少校驗工做,例如是否已經存在同名文件,是否有權限,若是驗證經過,返回一個FSDataOutputStream對象。 若是驗證不經過,拋出異常到客戶端。
3)客戶端寫入數據的時候,DFSOutputStream分解爲packets(數據包),並寫入到一個數據隊列中,該隊列由DataStreamer消費。
4)DateStreamer負責請求Namenode分配新的block存放的數據節點。這些節點存放同一個Block的副本,構成一個管道。 DataStreamer將packet寫入到管道的第一個節點,第一個節點存放好packet以後,轉發給下一個節點,下一個節點存放 以後繼續往下傳遞。
5)DFSOutputStream同時維護一個ack queue隊列,等待來自datanode確認消息。當管道上的全部datanode都確認以後,packet從ack隊列中移除。
6)數據寫入完畢,客戶端close輸出流。將全部的packet刷新到管道中,而後安心等待來自datanode的確認消息。所有獲得確認以後告知Namenode文件是完整的。 Namenode此時已經知道文件的全部Block信息(由於DataStreamer是請求Namenode分配block的),只需等待達到最小副本數要求,而後返回成功信息給客戶端。
Namenode如何決定副本存在哪一個Datanode?
HDFS的副本的存放策略是可靠性、寫帶寬、讀帶寬之間的權衡。默認策略以下:
這樣選擇很好滴平衡了可靠性、讀寫性能
一致性模型描述文件系統中讀寫操縱的可見性。HDFS中,文件一旦建立以後,在文件系統的命名空間中可見:
可是任何被寫入到文件的內容不保證可見,即便對象流已經被刷新。
「`java
Path p = new Path(「p」);
OutputStream out = fs.create(p);
out.write(「content」.getBytes(「UTF-8」));
out.flush();
assertTaht(fs.getFileStatus(p).getLen,0L); // 爲0,即便調用了flush
關閉對象流時,內部會調用hflush方法,可是hflush不保證datanode數據已經寫入到磁盤,只是保證寫入到datanode的內存, 所以在機器斷電的時候可能致使數據丟失,若是要保證寫入磁盤,使用hsync方法,hsync類型與fsync()的系統調用,fsync提交某個文件句柄的緩衝數據。
使用hflush或hsync會致使吞吐量降低,所以設計應用時,須要在吞吐量以及數據的健壯性之間作權衡。
另外,文件寫入過程當中,當前正在寫入的Block對其餘Reader不可見。
在讀取和寫入的過程當中,namenode在分配Datanode的時候,會考慮節點之間的距離。HDFS中,距離沒有
採用帶寬來衡量,由於實際中很難準確度量兩臺機器之間的帶寬。
Hadoop把機器之間的拓撲結構組織成樹結構,而且用到達公共父節點所需跳轉數之和做爲距離。事實上這是一個距離矩陣的例子。下面的例子簡明地說明了距離的計算:
同一數據中心,同一機架,同一節點距離爲0
同一數據中心,同一機架,不一樣節點距離爲2
同一數據中心,不一樣機架,不一樣節點距離爲4
不一樣數據中心,不一樣機架,不一樣節點距離爲6
Hadoop集羣的拓撲結構須要手動配置,若是沒配置,Hadoop默認全部節點位於同一個數據中心的同一機架上。
前面的關注點都在於單線程的訪問,若是須要並行處理文件,須要本身編寫應用。Hadoop提供的distcp工具用於並行導入數據到Hadoop或者從Hadoop導出。一些例子:
distcp是底層使用MapReduce實現,只有map實現,沒有reduce。在map中並行複製文件。 distcp儘量在map之間平均分配文件。map的數量能夠經過-m參數指定:
hadoop distcp -update -delete -p hdfs://master1:9000/foo hdfs://master2/foo
這樣的操做經常使用於在兩個集羣之間複製數據,update參數表示只同步被更新過的數據,delete會刪除目標目錄中存在,可是源目錄不存在的文件。p參數表示保留文件的全校、block大小、副本數量等屬性。
若是兩個集羣的Hadoop版本不兼容,可使用webhdfs協議:
hadoop distcp webhdfs://namenode1:50070/foo webhdfs://namenode2:50070/foo
在distcp工具中,若是咱們指定map數量爲1,不只速度很慢,每一個Block第一個副本將所有落到運行這個惟一map的節點上,直到磁盤溢出。所以使用distcp的時候,最好使用默認的map數量,即20. HDFS在Block均勻分佈在各個節點上的時候工做得最好,若是沒有辦法在做業中儘可能保持集羣平衡,例如爲了限制map數量(以便其餘節點能夠被別的做業使用),那麼可使用balancer工具來調整集羣的Block分佈