hdfs

計算機系統

Hadoop分散式文件系統(HDFS)被設計成適合運行在通用硬體(commodity hardware)上的分散式文件系統。它和現有的分散式文件系統有很多共同點。但同時,它和其他的分散式文件系統的區別也是很明顯的。HDFS是一個高度容錯性的系統,適合部署在廉價的機器上。HDFS能提供高吞吐量的數據訪問,非常適合大規模數據集上的應用。HDFS放寬了一部分POSIX約束,來實現流式讀取文件系統數據的目的。HDFS在最開始是作為Apache Nutch搜索引擎項目的基礎架構而開發的。HDFS是Apache Hadoop Core項目的一部分。

HDFS有著高容錯性(fault-tolerant)的特點,並且設計用來部署在低廉的(low-cost)硬體上。而且它提供高吞吐量(high throughput)來訪問應用程序的數據,適合那些有著超大數據集(large data set)的應用程序。HDFS放寬了(relax)POSIX的要求(requirements)這樣可以實現流的形式訪問(streaming access)文件系統中的數據。

特點和目標


硬體故障
硬體故障是常態,而不是異常。整個HDFS系統將由數百或數千個存儲著文件數據片段的伺服器組成。實際上它裡面有非常巨大的組成部分,每一個組成部分都很可能出現故障,這就意味著HDFS里的總是有一些部件是失效的,因此,故障的檢測和自動快速恢復是HDFS一個很核心的設計目標。
數據訪問
運行在HDFS之上的應用程序必須流式地訪問它們的數據集,它不是運行在普通文件系統之上的普通程序。HDFS被設計成適合批量處理的,而不是用戶互動式的。重點是在數據吞吐量,而不是數據訪問的反應時間,POSIX的很多硬性需求對於HDFS應用都是非必須的,去掉POSIX一小部分關鍵語義可以獲得更好的數據吞吐率。
大數據集
運行在HDFS之上的程序有很大量的數據集。典型的HDFS文件大小是GB到TB的級別。所以,HDFS被調整成支持大文件。它應該提供很高的聚合數據帶寬,一個集群中支持數百個節點,一個集群中還應該支持千萬級別的文件。
簡單一致性模型
大部分的HDFS程序對文件操作需要的是一次寫多次讀取的操作模式。一個文件一旦創建、寫入、關閉之後就不需要修改了。這個假定簡單化了數據一致的問題,並使高吞吐量的數據訪問變得可能。一個Map-Reduce程序或者網路爬蟲程序都可以完美地適合這個模型。
移動計算比移動數據更經濟
在靠近計算數據所存儲的位置來進行計算是最理想的狀態,尤其是在數據集特別巨大的時候。這樣消除了網路的擁堵,提高了系統的整體吞吐量。一個假定就是遷移計算到離數據更近的位置比將數據移動到程序運行更近的位置要更好。HDFS提供了介面,來讓程序將自己移動到離數據存儲更近的位置。
異構軟硬體平台間的可移植性
HDFS被設計成可以簡便地實現平台間的遷移,這將推動需要大數據集的應用更廣泛地採用HDFS作為平台。
名位元組點和數據節點
HDFS是一個主從結構,一個HDFS集群是由一個名位元組點,它是一個管理文件命名空間和調節客戶端訪問文件的主伺服器,當然還有一些數據節點,通常是一個節點一個機器,它來管理對應節點的存儲。HDFS對外開放文件命名空間並允許用戶數據以文件形式存儲。
內部機制是將一個文件分割成一個或多個塊,這些塊被存儲在一組數據節點中。名位元組點用來操作文件命名空間的文件或目錄操作,如打開,關閉,重命名等等。它同時確定塊與數據節點的映射。數據節點負責來自文件系統客戶的讀寫請求。數據節點同時還要執行塊的創建,刪除,和來自名位元組點的塊複製指令。
hdfs
hdfs
名位元組點和數據節點都是運行在普通的機器之上的軟體,機器典型的都是GNU/Linux,HDFS是用java編寫的,任何支持java的機器都可以運行名位元組點或數據節點,利用java語言的超輕便性,很容易將HDFS部署到大範圍的機器上。典型的部署是由一個專門的機器來運行名位元組點軟體,集群中的其他每台機器運行一個數據節點實例。體系結構不排斥在一個機器上運行多個數據節點的實例,但是實際的部署不會有這種情況。
集群中只有一個名位元組點極大地簡單化了系統的體系結構。名位元組點是仲裁者和所有HDFS元數據的倉庫,用戶的實際數據不經過名位元組點。

文件命名空間


HDFS支持傳統的繼承式的文件組織結構。一個用戶或一個程序可以創建目錄,存儲文件到很多目錄之中。文件系統的名字空間層次和其他的文件系統相似。可以創建、移動文件,將文件從一個目錄移動到另外一個,或重命名。HDFS還沒有實現用戶的配額和訪問控制。HDFS還不支持硬鏈接和軟鏈接。然而,HDFS結構不排斥在將來實現這些功能。
名位元組點維護文件系統的命名空間,任何文件命名空間的改變和或屬性都被名位元組點記錄。應用程序可以指定文件的副本數,文件的副本數被稱作文件的複製因子,這些信息由命名空間來負責存儲。

數據複製

HDFS設計成能可靠地在集群中大量機器之間存儲大量的文件,它以塊序列的形式存儲文件。文件中除了最後一個塊,其他塊都有相同的大小。屬於文件的塊為了故障容錯而被複制。塊的大小和複製數是以文件為單位進行配置的,應用可以在文件創建時或者之後修改複製因子。HDFS中的文件是一次寫的,並且任何時候都只有一個寫操作。
名位元組點負責處理所有的塊複製相關的決策。它周期性地接受集群中數據節點的心跳和塊報告。一個心跳的到達表示這個數據節點是正常的。一個塊報告包括該數據節點上所有塊的列表。
hdfs
hdfs
副本位置:第一小步
塊副本存放位置的選擇嚴重影響HDFS的可靠性和性能。副本存放位置的優化是HDFS區分於其他分散式文件系統的的特徵,這需要精心的調節和大量的經驗。機架敏感的副本存放策略是為了提高數據的可靠性,可用性和網路帶寬的利用率。副本存放策略的實現是這個方向上比較原始的方式。短期的實現目標是要把這個策略放在生產環境下驗證,了解更多它的行為,為以後測試研究更精緻的策略打好基礎。
HDFS運行在跨越大量機架的集群之上。兩個不同機架上的節點是通過交換機實現通信的,在大多數情況下,相同機架上機器間的網路帶寬優於在不同機架上的機器。
在開始的時候,每一個數據節點自檢它所屬的機架id,然後在向名位元組點註冊的時候告知它的機架id。HDFS提供介面以便很容易地掛載檢測機架標示的模塊。一個簡單但不是最優的方式就是將副本放置在不同的機架上,這就防止了機架故障時數據的丟失,並且在讀數據的時候可以充分利用不同機架的帶寬。這個方式均勻地將複製分散在集群中,這就簡單地實現了組建故障時的負載均衡。然而這種方式增加了寫的成本,因為寫的時候需要跨越多個機架傳輸文件塊。
默認的HDFS block放置策略在最小化寫開銷和最大化數據可靠性、可用性以及總體讀取帶寬之間進行了一些折中。一般情況下複製因子為3,HDFS的副本放置策略是將第一個副本放在本地節點,將第二個副本放到本地機架上的另外一個節點而將第三個副本放到不同機架上的節點。這種方式減少了機架間的寫流量,從而提高了寫的性能。機架故障的幾率遠小於節點故障。這種方式並不影響數據可靠性和可用性的限制,並且它確實減少了讀操作的網路聚合帶寬,因為文件塊僅存在兩個不同的機架,而不是三個。文件的副本不是均勻地分佈在機架當中,1/3在同一個節點上,1/3副本在同一個機架上,另外1/3均勻地分佈在其他機架上。這種方式提高了寫的性能,並且不影響數據的可靠性和讀性能。
副本的選擇
為了盡量減小全局的帶寬消耗讀延遲,HDFS嘗試返回給一個讀操作離它最近的副本。假如在讀節點的同一個機架上就有這個副本,就直接讀這個,如果HDFS集群是跨越多個數據中心,那麼本地數據中心的副本優先於遠程的副本。

安全模式

在啟動的時候,名位元組點進入一個叫做安全模式的特殊狀態。安全模式中不允許發生文件塊的複製。名位元組點接受來自數據節點的心跳和塊報告。一個塊報告包含數據節點所擁有的數據塊的列表。
每一個塊有一個特定的最小複製數。當名位元組點檢查這個塊已經大於最小的複製數就被認為是安全地複製了,當達到配置的塊安全複製比例時(加上額外的30秒),名位元組點就退出安全模式。它將檢測數據塊的列表,將小於特定複製數的塊複製到其他的數據節點。
文件系統的元數據的持久化
HDFS的命名空間是由名位元組點來存儲的。名位元組點使用叫做EditLog的事務日誌來持久記錄每一個對文件系統元數據的改變,如在HDFS中創建一個新的文件,名位元組點將會在EditLog中插入一條記錄來記錄這個改變。類似地,改變文件的複製因子也會向EditLog中插入一條記錄。名位元組點在本地文件系統中用一個文件來存儲這個EditLog。整個文件系統命名空間,包括文件塊的映射表和文件系統的配置都存在一個叫FsImage的文件中,FsImage也存放在名位元組點的本地文件系統中。
名位元組點在內存中保留一個完整的文件系統命名空間和文件塊的映射表的鏡像。這個元數據被設計成緊湊的,這樣4GB內存的名位元組點就足以處理非常大的文件數和目錄。名位元組點啟動時,它將從磁碟中讀取FsImage和EditLog,將EditLog中的所有事務應用到FsImage的仿內存空間,然後將新的FsImage刷新到本地磁碟中,因為事務已經被處理並已經持久化的FsImage中,然後就可以截去舊的EditLog。這個過程叫做檢查點。當前實現中,檢查點僅在名位元組點啟動的時候發生,正在支持周期性的檢查點。
數據節點將HDFS數據存儲到本地的文件系統中。數據節點並不知道HDFS文件的存在,它在本地文件系統中以單獨的文件存儲每一個HDFS文件的數據塊。數據節點不會將所有的數據塊文件存放到同一個目錄中,而是啟髮式的檢測每一個目錄的最優文件數,並在適當的時候創建子目錄。在本地同一個目錄下創建所有的數據塊文件不是最優的,因為本地文件系統可能不支持單個目錄下巨額文件的高效操作。當數據節點啟動的時候,它將掃描它的本地文件系統,根據本地的文件產生一個所有HDFS數據塊的列表並報告給名位元組點,這個報告稱作塊報告。

通信協議

所有的通信協議都是在TCP/IP協議之上構建的。一個客戶端和指定TCP配置埠的名位元組點建立連接之後,它和名位元組點之間通信的協議是Client Protocol。數據節點和名位元組點之間通過Datanode Protocol通信。
RPC(Remote Procedure Call)抽象地封裝了Client Protocol和DataNode Protocol協議。按照設計,名位元組點不會主動發起一個RPC,它只是被動地對數據節點和客戶端發起的RPC作出反饋。

異常處理


可靠性

HDFS的主要目標就是在存在故障的情況下也能可靠地存儲數據。三個最常見的故障是名位元組點故障,數據節點故障和網路斷開。

重新複製

一個數據節點周期性發送一個心跳包到名位元組點。網路斷開會造成一組數據節點子集和名位元組點失去聯繫。名位元組點根據缺失的心跳信息判斷故障情況。名位元組點將這些數據節點標記為死亡狀態,不再將新的IO請求轉發到這些數據節點上,這些數據節點上的數據將對HDFS不再可用,可能會導致一些塊的複製因子降低到指定的值。
名位元組點檢查所有的需要複製的塊,並開始複製他們到其他的數據節點上。重新複製在有些情況下是不可或缺的,例如:數據節點失效,副本損壞,數據節點磁碟損壞或者文件的複製因子增大。

數據正確性

從數據節點上取一個文件塊有可能是壞塊,壞塊的出現可能是存儲設備錯誤,網路錯誤或者軟體的漏洞。HDFS客戶端實現了HDFS文件內容的校驗。當一個客戶端創建一個HDFS文件時,它會為每一個文件塊計算一個校驗碼並將校驗碼存儲在同一個HDFS命名空間下一個單獨的隱藏文件中。當客戶端訪問這個文件時,它根據對應的校驗文件來驗證從數據節點接收到的數據。如果校驗失敗,客戶端可以選擇從其他擁有該塊副本的數據節點獲取這個塊。

元數據失效

FsImage和Editlog是HDFS的核心數據結構。這些文件的損壞會導致整個集群的失效。因此,名位元組點可以配置成支持多個FsImage和EditLog的副本。任何FsImage和EditLog的更新都會同步到每一份副本中。
同步更新多個EditLog副本會降低名位元組點的命名空間事務交易速率。但是這種降低是可以接受的,因為HDFS程序中產生大量的數據請求,而不是元數據請求。名位元組點重新啟動時,選擇最新一致的FsImage和EditLog。
名位元組點對於一個HDFS集群是單點失效的。假如名位元組點失效,就需要人工的干預。還不支持自動重啟和到其它名位元組點的切換。

特點


快照

快照支持在一個特定時間存儲一個數據拷貝,快照可以將失效的集群回滾到之前一個正常的時間點上。HDFS已經支持元數據快照。

數據組織

數據塊
HDFS的設計是用於支持大文件的。運行在HDFS上的程序也是用於處理大數據集的。這些程序僅寫一次數據,一次或多次讀數據請求,並且這些讀操作要求滿足流式傳輸速度。HDFS支持文件的一次寫多次讀操作。HDFS中典型的塊大小是64MB,一個HDFS文件可以被被切分成多個64MB大小的塊,如果需要,每一個塊可以分佈在不同的數據節點上。

階段狀態

一個客戶端創建一個文件的請求並不會立即轉發到名位元組點。實際上,一開始HDFS客戶端將文件數據緩存在本地的臨時文件中。應用程序的寫操作被透明地重定向到這個臨時本地文件。當本地文件堆積到一個HDFS塊大小的時候,客戶端才會通知名位元組點。名位元組點將文件名插入到文件系統層次中,然後為它分配一個數據塊。名位元組點構造包括數據節點ID(可能是多個,副本數據塊存放的節點也有)和目標數據塊標識的報文,用它回復客戶端的請求。客戶端收到后將本地的臨時文件刷新到指定的數據節點數據塊中。
當文件關閉時,本地臨時文件中未上傳的殘留數據就會被轉送到數據節點。然後客戶端就可以通知名位元組點文件已經關閉。此時,名位元組點將文件的創建操作添加到到持久化存儲中。假如名位元組點在文件關閉之前死掉,文件就丟掉了。
上述流程是在認真考慮了運行在HDFS上的目標程序之後被採用。這些應用程序需要流式地寫文件。如果客戶端對遠程文件系統進行直接寫入而沒有任何本地的緩存,這就會對網速和網路吞吐量產生很大的影響。這方面早有前車之鑒,早期的分散式文件系統如AFS,也用客戶端緩衝來提高性能,POSIX介面的限制也被放寬以達到更高的數據上傳速率。

流水式複製

當客戶端寫數據到HDFS文件中時,如上所述,數據首先被寫入本地文件中,假設HDFS文件的複製因子是3,當本地文件堆積到一塊大小的數據,客戶端從名位元組點獲得一個數據節點的列表。這個列表也包含存放數據塊副本的數據節點。當客戶端刷新數據塊到第一個數據節點。第一個數據節點開始以4kb為單元接收數據,將每一小塊都寫到本地庫中,同時將每一小塊都傳送到列表中的第二個數據節點。同理,第二個數據節點將小塊數據寫入本地庫中同時傳給第三個數據節點,第三個數據節點直接寫到本地庫中。一個數據節點在接前一個節點數據的同時,還可以將數據流水式傳遞給下一個節點,所以,數據是流水式地從一個數據節點傳遞到下一個。

可訪問性


HDFS提供多種方式由應用程序訪問,自然地,HDFS提供為程序提供java api,為c語言包裝的java api也是可用的,還有一個HTTP瀏覽器可以瀏覽HDFS中的文件,通過WebDAV協議訪問HDFS庫的方式也正在構建中。

DFSShell

HDFS允許用戶數據組織成文件和文件夾的方式,它提供一個叫DFSShell的介面,使用戶可以和HDFS中的數據交互。命令集的語法跟其他用戶熟悉的shells(bash,csh)相似。以下是一些例子:
ActionCommand
創建目錄 /foodirhadoop dfs -mkdir /foodir
查看文件 /foodir/myfile.txthadoop dfs -cat /foodir/myfile.txt
刪除文件/foodir/myfile.txthadoop dfs -rm /foodir myfile.txt
DFSAdmin
DFSAdmin命令集是用於管理dfs集群的,這些命令只由HDFS管理員使用。示例:
ActionCommand
將集群設置成安全模式bin/hadoop dfsadmin -safemode enter
產生一個數據節點的列表bin/hadoop dfsadmin -report
去掉一個數據節點bin/hadoop dfsadmin -decommission datanodename

瀏覽器介面

典型的HDFS初始化配置了一個web 服務,通過一個可配的TCP埠可以訪問HDFS的命名空間。這就使得用戶可以通過web瀏覽器去查看HDFS命名空間的內容。

存儲空間回收


文件刪除和恢復刪除
當一個文件被用戶或程序刪除時,它並沒有立即從HDFS中刪除。HDFS將它重新命名後轉存到/trash目錄下,這個文件只要還在/trash目錄下保留就可以重新快速恢復。文件在/trash中存放的時間是可配置的。存儲時間超時后,名位元組點就將目標文件從名字空間中刪除,同時此文件關聯的所有文件塊都將被釋放。注意,用戶刪除文件的時間和HDF系統回收空閑存儲之間的時間間隔是可以估計的。
刪除一個文件之後,只要它還在/trash目錄下,用戶就可以恢復刪除一個文件。如果一個用戶希望恢復刪除他已經刪除的文件,可以查找/trash目錄獲得這個文件。/trash目錄僅保存最新版本的刪除文件。/trash目錄也像其他目錄一樣,只有一個特殊的功能,HDFS採用一個特定的策略去自動地刪除這個目錄里的文件,當前默認的策略是刪除在此目錄存放超過6小時的文件。以後這個策略將由一個定義好的介面來配置。
減少複製因子
當文件的複製因子減少了,名位元組點選擇刪除多餘的副本,下一次的心跳包的回復就會將此信息傳遞給數據節點。然後,數據節點移除相應的塊,對應的空閑空間將回歸到集群中,需要注意的就是,在setReplication函數調用后和集群空閑空間更新之間會有一段時間延遲。

讀取解析


文件內容讀取的代碼可以分為三個大步驟。
1、獲取文件系統
2、通過文件系統打開文件
3、將文件內容輸出
接下來,我們來看一下每個步驟的詳細過程

獲取文件系統對象

要從HDFS上讀取文件,必須先得到一個FileSystem。HDFS本身就是一個文件系統,所以,我們得到一個文件系統后就可以對HDFS進行相關操作。獲取文件系統的步驟可以分為以下2步。
1、讀取配置文件。
2、獲取文件系統。
讀取配置文件:Configuration類有三個構造器,無參數的構造器表示直接載入默認資源,也可以指定一個boolean參數來關閉載入默認值,或直接使用另外一個Configuration對象來初始化。

打開文件

打開文件其實就是創建一個文件輸入流,跟蹤文件系統的open方法,可以找到源碼
再跟蹤open方法,找到以下抽象方法。
在返回結果的時候,創建了一個FileSystemLinkResolver對象,並實現了此類的兩個抽象方法。doCall方法和next方法都在resolve方法里用到了,而next方法只是在resolve方法異常捕獲時才調用。
跟蹤doCall方法,doCall方法里的open()方法有3個參數,src表示要打開的文件路徑,buffersize表示緩衝大小,verifyChecksum表示是否校驗和,的源代碼如下。
checkOpen方法表示檢查文件系統是否已經打開,如果沒有打開,則拋出異常(FileSystemclosed)。
然後返回一個分散式文件系統輸入流(DFSInputStream),此處調用的構造方法源代碼如下。
這個方法先是做了一些準備工作,然後調用openInfo()方法,openInfo()方法是一個線程安全的方法,作用是從namenode獲取已打開的文件信息。其源代碼如下。
此方法有調用fetchLocatedBlocksAndGetLastBlockLength()方法獲取塊的位置信息。
getLocatedBlocks方法可以獲取塊的位置信息。LocatedBlocks類是許多塊的位置信息的集合。因為從此類的源碼可以發現有這個一個私有屬性:
通過文件名,FSDataInputStream類可以獲取相文件內容,也可以充當namenode與datanode橋樑。

內容輸出顯示

因為之前已經獲得了一個FSDataInputStream,所以,我們可以調用方法copyBytes將FSDataInputStream拷貝到標準輸出流System.out顯示。
此方法里又調用了另外一個copyBytes方法,作用同樣是從一個流拷貝到另外一個流。
先從輸入流中讀取buffSize大小的數據到緩衝裡面,然後將緩衝里的數據寫入到輸出流out里。一直循環,直到從輸入流中讀到緩衝里的位元組長度為0,表示輸入流里的數據已經讀取完畢。