特點目標
硬體故障
硬體故障是常態,而不是異常。整個HDFS系統將由數百或數千個存儲著檔案數據片斷的伺服器組成。實際上它裡面有非常巨大的組成部分,每一個組成部分都很可能出現故障,這就意味著HDFS里的總是有一些部件是失效的,因此,故障的檢測和自動快速恢復是HDFS一個很核心的設計目標。
數據訪問
運行在HDFS之上的應用程式必須流式地訪問它們的數據集,它不是運行在普通檔案系統之上的普通程式。HDFS被設計成適合批量處理的,而不是用戶互動式的。重點是在數據吞吐量,而不是數據訪問的反應時間,POSIX的很多硬性需求對於HDFS套用都是非必須的,去掉POSIX一小部分關鍵語義可以獲得更好的數據吞吐率。
大數據集
運行在HDFS之上的程式有很大量的數據集。典型的HDFS檔案大小是GB到TB的級別。所以,HDFS被調整成支持大檔案。它應該提供很高的聚合數據頻寬,一個集群中支持數百個節點,一個集群中還應該支持千萬級別的檔案。
簡單一致性模型
大部分的HDFS程式對檔案操作需要的是一次寫多次讀取的操作模式。一個檔案一旦創建、寫入、關閉之後就不需要修改了。這個假定簡單化了數據一致的問題和並使高吞吐量的數據訪問變得可能。一個Map-Reduce程式或者網路爬蟲程式都可以完美地適合這個模型。
移動計算比移動數據更經濟
在靠近計算數據所存儲的位置來進行計算是最理想的狀態,尤其是在數據集特別巨大的時候。這樣消除了網路的擁堵,提高了系統的整體吞吐量。一個假定就是遷移計算到離數據更近的位置比將數據移動到程式運行更近的位置要更好。HDFS提供了接口,來讓程式將自己移動到離數據存儲更近的位置。
異構軟硬體平台間的可移植性
HDFS被設計成可以簡便地實現平台間的遷移,這將推動需要大數據集的套用更廣泛地採用HDFS作為平台。
名位元組點和數據節點
HDFS是一個主從結構,一個HDFS集群是由一個名位元組點,它是一個管理檔案命名空間和調節客戶端訪問檔案的主伺服器,當然還有一些數據節點,通常是一個節點一個機器,它來管理對應節點的存儲。HDFS對外開放檔案命名空間並允許用戶數據以檔案形式存儲。內部機制是將一個檔案分割成一個或多個塊,這些塊被存儲在一組數據節點中。名位元組點用來操作檔案命名空間的檔案或目錄操作,如打開,關閉,重命名等等。它同時確定塊與數據節點的映射。數據節點負責來自檔案系統客戶的讀寫請求。數據節點同時還要執行塊的創建,刪除,和來自名位元組點的塊複製指令。
名位元組點和數據節點都是運行在普通的機器之上的軟體,機器典型的都是GNU/Linux,HDFS是用java編寫的,任何支持java的機器都可以運行名位元組點或數據節點,利用java語言的超輕便型,很容易將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 Protocal。數據節點和名位元組點之間通過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提供為程式提供javaapi,為c語言包裝的javaapi也是可用的,還有一個HTTP瀏覽器可以瀏覽HDFS中的檔案,通過WebDAV協定訪問HDFS庫的方式也正在構建中。
DFSShell
HDFS允許用戶數據組織成檔案和資料夾的方式,它提供一個叫DFSShell的接口,使用戶可以和HDFS中的數據互動。命令集的語法跟其他用戶熟悉的shells(bash,csh)相似。以下是一些例子:
Action | Command |
創建目錄 /foodir | hadoop dfs -mkdir /foodir |
查看檔案 /foodir/myfile.txt | hadoop dfs -cat /foodir/myfile.txt |
刪除檔案/foodir/myfile.txt | hadoop dfs -rm /foodir myfile.txt |
DFSAdmin命令集是用於管理dfs集群的,這些命令只由HDFS管理員使用。示例:
Action | Command |
將集群設定成安全模式 | 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,表示輸入流里的數據已經讀取完畢。