Hadoop分布式文件系統(HDFS)的設計主旨,在于對超大規模數據集提供可靠的存儲功能,并對用戶應用程序提供高帶寬的輸入輸出數據流。在大型的集群里,上千臺服務器均可直接參與到數據存儲和應用程序任務執行。通過多服務器,分布式的存儲和計算,計算資源的規模能夠按照需要增長,并兼顧在各種規模上經濟適用性。 本文主要描述了HDFS的架構,并以Yahoo!企業數據服務為例,介紹了如何使用HDFS系統管理高達4O PB規模的數據庫的經驗。
**8.1 介紹**
Hadoop采用MapReduce范式[DG04]進行設計,提供了一套分布式文件系統和框架,用以對超大規模的數據集進行分析和變換。HDFS的接口沿襲了Unix文件系統的設計模式,但在其基礎上做出了改進,以提高在實際應用中的訪問性能。
Hapoop所具有一個重要特點,就是把數據和運算分開,并將二者分布存放在數以千計的服務器主機上,應用程序計算以及相關數據都以并行方式處理。一個Hadoop集群可以僅僅通過增加普通服務器的方式,來擴展其運算、存儲和I/O帶寬的規模。Yahoo所使用的Hadoop集群組,共包含40,000臺服務器,存儲并處理多達40 PB(1 Petabytes = 1000000000000000 字節)的應用數據,其中最大的單個集群,使用多達4000個服務器。此外,在世界范圍內,還有100多家其他的組織和機構表示,他們也使用Hadoop來進行數據存儲和處理。
HDFS將文件系統元數據(File System Metadata)和應用數據(Application Data)分離存放。與其他種類的分布式文件系統類似,例如PVFS[CIRT00], Lustre2, 以及GFS[GGL03],HDFS將元數據存放在專用服務器上,該服務器稱為“NameNode”(名稱節點);應用數據被存放在其他的服務器上,這些服務器稱為“DataNode”(數據節點)。在該分布式系統中,各個服務器之間均通過網絡連接,確保節點之間可以通過基于TCP族的協議進行相互通信。HDFS不像Lustre或者PVFS,它并不依賴于數據保護機制(例如RAID)來確保數據的穩定性,而是像GFS那樣,在多個DataNode節點上保存數據的多個副本,以此來確保數據的穩定。采用這樣的策略,其好處不僅僅在于數據安全方面,在數據傳輸帶寬方面,由于一個數據有多個副本,因此可以通過多線程訪問倍速提高帶寬(就像迅雷下載的原理一樣——譯者注),并且采用此種方式還可以提高從較近的服務器節點上獲取數據的幾率。
**8.2 架構**
**8.2.1 NameNode(名稱節點)**
HDFS命名空間采用層次化(樹狀——譯者注)的結構存放文件和目錄。文件和目錄用NameNode上的inodes表示。Inode記錄了權限,修改和訪問時間,命名空間,磁盤容量等屬性。文件內容會被分成不同的“大塊”(典型分塊策略是每塊128M,不過用戶可以對每個文件的分塊大小進行選擇)。NameNode負責維護命名空間樹以及與DataNode上文件分塊的映射關系。目前采用的設計結構是,沒一個集群只有一個NameNode,一個NameNode可以對應多個DataNode以及成千上萬的HDFS客戶端。一個DataNode可以同步執行多個應用任務。
**8.2.2 映像和日志**
Inode和定義metadata的系統文件塊列表統稱為Image(映像). NameNode將整個命名空間映像保存在RAM中。而映像的持久化記錄則保存在NameNode的本地文件系統中,該持久化記錄被稱為Checkpoint(檢查點)。NameNode還會記錄HDFS中寫入的操作,并將其存入一個記錄文件,存放在本地文件系統中,這個記錄文件被叫做Journal(日志)。存放塊位置的副本不屬于持久化檢查點(persistent checkpoint)的一個部分。 每個客戶端發起的事務都會被記錄到日志里,然后日志文件會被刷新和同步,再發送回客戶端。NameNode上的檢查點文件(Checkpoint file)一旦生成,就不允許再修改。如果NameNode重啟,在系統管理員的要求下,或者根據CheckpointNode的定義(下章介紹),可以生成一個新的文件記錄checkpoint。 在啟動過程中,NameNode會從checkpoint中初始化命名空間映像,然后根據日志重現所有的寫入更改操作。在NameNode開始響應客戶端之前,一個新的checkpoint和一個空的日志將被保存到存儲目錄當中。
為了提高持久性,系統會將checkpoint文件和日志的多個冗余備份存儲到多個獨立的本地卷以及遠程NFS服務器上。之所以存儲到獨立卷標,是為了避免單個卷標失效后造成文件丟失;存儲到遠程服務器則是為了預防整個節點崩潰后造成所有本地文件丟失。如果NameNode遇到了錯誤,無法將日志信息寫入到某個存儲目錄,那么系統就會自動將該有問題的目錄排除到存儲目錄列表的范圍之外。如果NameNode發現連一個可用的存儲目錄都找不到,則會執行自動關閉操作(節點失效)。
NameNode是一個多線程的系統應用,可以同時處理多個客戶端的申請。不過,將事務存儲到磁盤是一個較大的性能瓶頸,因為如果有一個線程正在存儲中,其他線程都必須等待該線程完成其刷新和同步過程完成后,才能繼續進行操作。為了優化這一過程,NameNode采用將多個事務批處理的方式,當某個NameNode線程初始化了一個刷新同步操作時,所有的事務會被一次性批處理,然后一起提交。其他的線程只需要檢查他們的事務是否被存儲了即可,而不需要再去提交刷新同步操作。
**8.2.3 數據節點**
DataNode上的每一個塊(block)副本都由兩個本地文件系統上的文件共同表示。其中一個文件包含了塊(block)本身所需包含的數據,另一個文件則記錄了該塊的元數據,包括塊所含數據大小和文件生成時間戳。數據文件的大小等于該塊(block)的真實大小,而不是像傳統的文件系統一樣,需要用額外的存儲空間湊成完整的塊。因此,如果一個塊里只需要一半的空間存儲數據,那么就只需要在本地系統上分配半塊的存儲空間即可。
在啟動過程中,每個DataNode通過“握手”的方式與另外一個NameNode節點連接。之所以采用“握手”方式,是為了驗證DataNode的命名空間ID以及軟件的版本號。如果一個節點的ID或者版本號不匹配,那么DataNode節點就會自動關閉。
命名空間ID是在文件系統實例格式化的時候就分配好的。命名空間ID被在集群內的所有節點上都有持久化存儲。由于不同命名空間ID的節點無法加入到集群中,因此能夠保證集群文件系統的統一性。一個DataNode在剛初始化的時候沒有命名空間ID,此時該節點被允許加入集群,一旦加入,該節點就會以加入的集群的ID作為自己的ID。
“握手”之后,DataNode被注冊到NameNode。DataNode持久化保存其唯一的存儲ID(storage ID)。存儲ID是一個DataNode的內部標識符,該標識符能夠確保即使是服務器用不同的IP地址或者端口啟動,仍然可以被識別。存儲ID在DataNode首次注冊到NameNode時即被分配,一旦分配后便無法更改。
DataNode采用發送“塊報告”(block report)的形式,向NameNode標識其所包含的塊副本。塊報告包含了塊ID,生成時間戳,以及每個塊副本的長度等等。 首個塊報告會在DataNode注冊后立即發送。隨后的塊報告會每小時發送一次,以確保NameNode能夠知道集群中塊副本的最新情況。
在正常情況下,DataNode想NameNode發送“心跳信號”,以確認DataNode運行正常,以及其所包含的塊數據可用。默認的“心跳信號”的時間間隔是3秒。如果NameNode長達10分鐘沒有接受到來自于DataNode的心跳信號,那么久會認為為該DataNode節點已經失效,其所包含的塊(block)已經無法使用。接下來NameNode就會計劃在其他的DataNode上創建新的塊數據。
來自DataNode的心跳信號也會附帶包括總存儲容量,存儲使用量,當前數據傳輸進度等附加信息。這些統計數據可用于NameNode塊分配,以及作為負載均衡決策的參考。
NameNode不能直接向DataNode發送請求。它只通過回復心跳信號的方式來向DataNode發送指令。指令的內容包括,將塊移到其他節點,移除本地塊副本,重新注冊和發送即時塊報告,關閉當前節點等等。
這些命令對于維護整個系統的完整性來說非常關鍵,因此即使是在超大集群上,保持心跳信號的頻率也是至關重要的。NameNode每秒能夠處理上千條心跳信號,并且不影響到NameNode的其他正常操作。
**8.2.4 HDFS客戶端**
用戶應用程序通過HDFS客戶端連接到HDFS文件系統,通過庫文件可導出HDFS文件系統的接口。
像很多傳統的文件系統一樣,HDFS支持文件的讀、寫和刪除操作,還支持對目錄的創建和刪除操作。用戶通過帶命名空間的路徑對文件和目錄進行引用。用戶程序不需要知道文件系統的元數據和具體存儲在哪個服務器上,也不需要關心一個塊有多少個副本。
當一個應用程序讀一個文件的時候,HDFS客戶端首先向NameNode索要包含該文件的文件塊的DataNode節點的列表。該列表會按照網絡拓撲距離的遠近進行排序。然后客戶端會直接與相應的DataNode節點進行聯系,要求傳輸所需的文件塊。當客戶端寫一個文件的時候,它會首先要求NameNode選擇一個DataNode,該DataNode需要包含所寫入的文件的首個文件塊。接下來,客戶端會搭建一個從節點到節點的通信管道,用以進行數據傳輸。當第一個塊被寫入后,客戶端會申新的DataNode,用以寫入下一個塊。此時,新的通信管線建立,客戶端會通過管線寫入更多的數據。每個文件塊所寫入的DataNode節點也許會完全不同。客戶端,NameNode和DataNode之間的關系如圖8.1所示。

與傳統的文件系統不同的是,HDFS提供一個API用以暴露文件塊的位置。這個功能允許應用程序,例如MapReduce框架,去數據所存放的地點進行任務調度,以此來提高讀數據的新能。API也允許一個應用程序設定文件的復制因子。默認情況下,文件的復制因子是3,。對于關鍵的文件或者使用頻率較多的文件,使用更高的復制因子,能夠提高容錯性,以及提升文件的訪問帶寬。
**8.2.5 檢查點節點**
HDFS中的NameNode節點,除了其主要職責是相應客戶端請求以外,還能夠有選擇地扮演一到兩個其他的角色,例如做檢查點節點或者備份節點。該角色是在節點啟動的時候特有的。
檢查點節點定期地域已經存在的檢查點和日志一起,創建新的檢查點和空日志。檢查點節點通常運行于一個非NameNode節點的主機上,但它和NameNode節點擁有相同的內存需求配置。檢查點節點從NameNode上下載當前的檢查點和日志文件,將其在本地進行合并,并將新的檢查點返回到NameNode.
創建一個定期檢查點是保護文件系統元數據的一種方式。如果命名空間映像中的所有其他持久化拷貝均無法使用,系統還能夠從最近一次的checkpoint文件中啟動。當創建一個新的checkpoint被更新到NameNode的時候,還能讓NameNode產生截斷日志的效果。HDFS集群組可以長時間持續運行,無需重啟,但這也導致了系統日志的大小會不斷增長。當系統日志大到一定程度的時候,日志文件丟失或者損壞的幾率就會增加。所以,一個日志太大的節點需要重啟一下來對日志文件進行更新(截斷)。對于一個較大的集群來說,平均處理一周的日志內容需要耗費一小時的時間。所以較好的頻率是,每天創建一次新日志。
**8.2.6 備份節點**
HDFS的備份節點是最近在加入系統的一項特色功能。就像CheckpintNode一樣,備份節點能夠定期創建檢查點,但是不同的是,備份節點一直保存在內存中,隨著文件系統命名空間的映像更新和不斷更新,并與NameNode的狀態隨時保持同步。
備份節點從活動的NameNode節點中接受命名空間事務的日志流,并將它們以日志的形式存儲在其自身所帶的存儲目錄里,并使用自身的內存和命名空間映像來執行這個事務。NameNodez則將BackupNode當做日志一樣看待,就仿佛是存儲在其自身的存儲目錄里一樣。如果NameNode失效,那么BackupNode節點內存中的映像和磁盤上的checkpoint文件就可以作為最近的命名空間狀態的備份,以備還原。
BackupNode能夠動態創建一個checkpoint,而不需要從活動的NameNode上下載其checkpoint文件和日志文件。因為BackupNode始終把最新的狀態保存在它自身命名空間的內存中。這一特性使得在BackupNode節點上處理checkpoint變得非常高效,因為只需要把命名空間存儲到本地服務器就可以了,而不需要和NameNode再進行交互。
BackupNode還可以被看做是一個只讀的NameNode. 它包含了除文件塊位置以外的所有文件系統的元數據信息。除了修改命名空間或者文件塊位置以外,BackupNode可以做NameNode所能做的所有操作。使用BackupNode能夠使NameNode在運行的時候不進行持久化存儲,從而把持久化命名空間狀態的任務挪到BackupNode節點上進行。
**8.2.7 系統更新和文件系統快照**
在軟件更新的過程中,由于軟件的bug或者人為操作的失誤,文件系統損壞的幾率會隨之提升。在HDFS中創建系統快照的目的,就在于把系統升級過程中可能對數據造成的隱患降到最低。
快照機制讓系統管理員將當前系統狀態持久化到文件系統中,這樣以來,如果系統升級后出現了數據丟失或者損壞,便有機會進行回滾操作,將HDFS的命名空間和存儲狀態恢復到系統快照進行的時刻。
系統快照(只能有一個)在系統啟動后,根據集群管理員的選擇可隨時生成。如果要求生成一個系統快照,NameNode首先會讀取checkpoint和日志文件,并將其在內存中合并。然后,NameNode在新的位置創建并寫入一個新的checkpoint和空的日志,保證舊的Checkpoint和日志不會被改變。
在進行“握手”方式通信時,NameNode指示DataNode是否要創建一個本地的系統快照。DataNode上的本地系統快照不會復制本地的存儲目錄中包含的數據信息,因為如果這么做的話,會使得每個集群上的DataNode上的數據占據雙倍的存儲空間。因此,取而代之的策略是,建立一份目錄結構的副本,并用“鏈接”的方式將已經存在的塊文件指向到目錄副本。當DataNode嘗試移除一個文件塊時,只需要移除鏈接就可以了,在文件塊增量時,也是使用copy-on-wirte技術。所以舊的塊副本在其原先的目錄結構中保持不變。
集群管理員可以選擇在重啟系統時,將HDFS回滾到快照所表示的狀態。NameNode在快照恢復時,會恢復所記錄的checkpoint。DataNode則會恢復之前被更名的文件目錄,并初始化一個幕后進程,用以刪除在snapshot創建之后系統新增的文件塊副本。選擇回滾后,回滾之前的操作將無法再繼續(無法前滾)。集群管理員可以下達放棄快照的指令,來恢復被快照功能所占用的存儲權。如果再系統升級期間進行快照,那么升級過程將被終止。
隨著系統的升級,NameNode的checkpoint文件和日志文件的格式可能會發生變化,或者塊文件的數據結構也可能發生改變。因此,系統中使用“設計版本號”(layout version)來標識數據表現格式,該版本號被持久化地存儲在NameNode以及DataNode的存儲目錄中。每個節點在啟動時,會將其系統設計版本號和存儲目錄中的設計版本號進行對比,并自動嘗試將舊的數據格式轉換到新的版本。為了實現系統版本升級,新版本重啟時會強制創建系統快照。
**8.3 文件 I/O 操作和復制管理**
當然,一個文件系統的根本任務是用來存儲數據和文件。如果要理解HDFS如何完成這一基本任務,我們就必須從它如何進行數據的讀寫,以及文件塊如何管理這兩方面來說明。
**8.3.1 文件讀寫**
應用程序通過創建新文件以及向新文件寫數據的方式,給HDFS系統添加數據。文件關閉以后,被寫入的數據就無法再修改或者刪除,只有以“追加”方式重新打開文件后,才能再次為文件添加數據。HDFS采用單線程寫,多線程讀的模式。
HDFS客戶端需要首先獲得對文件操作的授權,然后才能對文件進行寫操作。在此期間,其他的客戶端都不能對該文件進行寫操作。被授權的客戶端通過向NameNode發送心跳信號來定期更新授權的狀態。當文件關閉時,授權會被回收。文件授權期限分為軟限制期和硬限制期兩個等級。當處于軟限制期內時,寫文件的客戶端獨占對文件的訪問權。當軟限制過期后,如果客戶端無法關閉文件,或沒有釋放對文件的授權,其他客戶端即可以預定獲取授權。當硬限制期過期后(一小時左右),如果此時客戶端還沒有更新(釋放)授權,HDFS會認為原客戶端已經退出,并自動終止文件的寫行為,收回文件控制授權。文件的寫控制授權并不會阻止其他客戶端對文件進行讀操作。因此一個文件可以有多個并行的客戶端對其進行讀取。
HDFS文件由多個文件塊組成。當需要創建一個新文件塊時,NameNode會生成唯一的塊ID,分配塊空間,以及決定將塊和塊的備份副本存儲到哪些DataNode節點上。DataNode節點會形成一個管道,管道中DataNode節點的順序能夠確保從客戶端到上一DataNode節點的總體網絡距離最小。文件的則以有序包(sequence of packets)的形式被推送到管道。應用程序客戶端創建第一個緩沖區,并向其中寫入字節。第一個緩沖區被填滿后(一般是64 KB大小),數據會被推送到管道。后續的包隨時可以推送,并不需要等前一個包發送成功并發回通知(這被稱為“未答復發送”——譯者注)。不過,這種未答復發送包的數目會根據客戶端所限定的“未答復包窗口”(outstanding packets windows)的大小進行限制。
在數據寫入HDFS文件后,只要文件寫操作沒有關閉,HDFS就不保證數據在此期間對新增的客戶端讀操作可見。如果客戶端用戶程序需要確保對寫入數據的可見性,可以顯示地執行hflush操作。這樣,當前的包就會被立即推送到管道,并且hflush操作會一直等到所有管道中的DataNode返回成功接收到數據的通知后才會停止。如此就可以保證所有在執行hflush之前所寫入的數據對試圖讀取的客戶端用戶均可見。

如果沒有錯誤發生,文件塊會經歷如圖8.2所示的3個步驟,該圖演示了一個三DataNode節點的管道以及其通信的過程。在圖中,實線代表數據包,虛線代表確認信息,細線代表控制信息,用以創建和關閉管道。豎直線代表客戶端和三個DataNode節點的活動,其時間順序是從上至下的。從t0到t1是管道的建立階段,t1是第一個數據包發送的時刻,t2是最后一個數據包接受成功并返回通知的時刻。在傳送數據包2的時候有一次hflush操作。Hflush說明數據包的傳輸并不是一個單獨分離的過程。T2和t3之間是該文件塊在管道中的關閉階段。
在一個集群的數千個節點里,節點失效(往往是因為存儲故障造成的)每天都有可能發生。DataNode中所包含的文件塊備份可能會因為內存、磁盤或者網絡的錯誤而造成損壞。為了避免這種錯誤的形成,HDFS會為其文件的每個數據塊生成并存儲一份Checksum(總和檢查)。Checksum主要供HDFS客戶端在讀取文件時檢查客戶端,DataNode以及網絡等幾個方面可能造成的數據塊損壞。當客戶端開始建立HDFS文件時,會檢查文件的每個數據塊的checksum序列,并將其與數據一起發送給DataNode。 DataNode則將checksum存放在文件的元數據文件里,與數據塊的具體數據分開存放。當HDFS讀取文件時,文件的每個塊數據和checksum均被發送到客戶端。客戶端會即時計算出接受的塊數據的checksum, 并將其與接受到的checksum進行匹配。如果不匹配,客戶端會通知NameNode,表明接受到的數據塊已經損壞,并嘗試從其他的DataNode節點獲取所需的數據塊。
當客戶端打開一個文件進行讀取時,會從NameNode中獲得一個文件數據塊列表,列表中包含了每一個所需的數據塊的具體位置。這些位置會按照與客戶端的距離進行排序。當具體進行數據塊讀取時,客戶端總是嘗試首先從最近的位置獲取數據。如果嘗試失敗,客戶端會根據排序的順尋,從下一個位置獲取數據。下列情況可能會造成數據讀取失敗:DataNode不可用,節點不再包含所需數據塊,或者數據塊備份損壞,以及checksum驗證失敗。
HDFS允許客戶端從正在進行寫操作的文件中讀取數據。當進行這樣的操作時,目前正在被寫入的數據塊對于NameNode來說是未知的。在這樣的情況下,客戶端會從所有數據塊備份中挑選一個數據塊,以這個數據塊的最后長度作為開始讀取數據之前的數據長度。
HDFS I/O的設計是專門針對批處理系統進行優化的,比如MapReduce系統,這類系統對順序讀寫的吞吐量都有很高的要求。針對于那些需要實時數據流以及隨機讀寫級別的應用來說,系統的讀/寫響應時間還有待于優化,目前正在做這方面的努力。
**8.3.2 數據塊部署**
對于巨大的集群來說,把所有的節點都部署在一個平行的拓撲結構里是不太現實的。比較實際且通用的做法是,把所有的節點分布到多個Rack(服務器機架)上。每個Rack上的節點共享一個交換機,Rack之間可以使用一個或者多個核心交換機進行互聯。在大多數情況下,同一Rack中的節點間通信的帶寬肯定會高于不同Rack間節點的通信帶寬。圖8.3描述了一個擁有兩個Rack的集群,每個Rack內部包含3個節點。

HDFS默認兩個節點之間的網絡帶寬與他們的物理距離成正比。從一個節點到其父節點的距離被認為是常數1。這樣,兩個節點之間的距離可以通過將其到各級祖先節點的距離相加計算出來。兩個節點之間的距離越短,就意味著他們之間傳輸數據的帶寬越大。
HDFS允許管理員通過配置腳本,返回一個節點的rack標識符,作為節點地址。NameNode位于整個結構的最中央,負責解析每一個DataNode的rack位置。當DataNode注冊到NameNode時,NameNode會運行這些配置腳本,確定節點屬于哪個rack。如果沒有進行腳本配置,NameNode則會認為所有的節點都屬于一個默認的Rack。
數據塊備份的部署對于HDFS數據的可靠性和讀寫性能都有至關重要的影響。良好的數據塊部署策略能夠有效地改進數據的可靠性,可用性,甚至提高網絡帶寬的利用率。目前的HDFS系統提供了可配置的數據塊部署策略接口,以此來讓用戶和研究人員能夠對不同的部署策略進行測驗,從而達到對系統應用進行優化的目的。
缺省的HDFS數據塊部署策略企圖在降低數據寫入代價,最大化數據可靠性,可用性,以及整合讀數據帶寬等幾個方面做出權衡。當一個新的數據塊被創建,HDFS會把第一個數據開備份放到寫入程序所在的位置。第二個和第三個數據塊備份會被部署到不同rack的其他兩個不同的節點。剩余的數據塊備份則被放到隨機的節點上,但是限制每個節點不會部署多于一個相同的數據塊,每個rack不會部署都與兩個相同的數據塊(如果條件滿足的話)。之所以要把第二個和第三個數據塊備份放到不同的rack上,是為了考慮到一個集群上的文件所應當具有的分布性。如果頭兩個數據塊備份放到相同的rack上,那么對于任何文件來說,其2/3的文件塊會被存放在同一rack上。
在所有目標節點都被選擇后,這些節點會被有組織地按照其親近程度,以流水線的方式被傳輸到第一個備份上。數據會被以這個順序推送到節點。在讀取的時候,NameNode首先會檢查客戶端所對應的主機是否位于集群當中。如果是,那么數據塊的位置會被返回到客戶端,并以按照距離遠近排序。然后數據塊就會按照順序從DataNode中進行讀取。
這一策略會降低rack之間以及節點之間的寫入時間,普遍提高寫入效率。因為rack故障的幾率遠低于節點故障的幾率,所以該策略不會影響到數據的有效性和可用性。在大多數使用3數據塊備份的情況下,該策略能夠降低網絡帶寬的消耗,因為一個數據塊只需要部署到兩個不同的rack上,而不是3個。
**8.3.3 數據塊備份管理**
NameNode會盡力保證們每個數據塊都有所需的備份數量。當Block Report從DataNode提交上來的時候,NameNode會偵測到數據塊的備份數量是少于所需還是超過所需。當超過時,NameNode會選擇刪除一個數據備份。NameNode傾向于不減少rack的數量,并在DataNode中選擇一個剩余磁盤空間最小的節點進行備份移除。這樣做的主要目的是平衡利用DataNode的存儲空間,并其不降低到數據塊的可用性。
當一個數據塊處于低于其備份需求數的狀態時,該數據塊就會被放入到備份優先隊列中。僅擁有一個數據備份的數據塊處于最高優先級,其數據備份數高于其備份因子2/3的數據塊則處于最低優先級。有一個后臺進程專門負責定期對備份優先隊列進行掃面,以確定將備份部署到何處。數據塊備份遵循與數據塊部署相似的策略。如果數據塊當前只有一個備份,那么HDFS會把一個新的備份放到不同rack上。如果數據塊當前有兩個備份,并且連個備份都存在與相同的rack上,第三個備份就會被放到不同的rack上。否則,第三個備份就被放到同一rack的不同節點上。這么做的目的也是為了降低創建備份的代價。
NameNode也會確保不把所有的數據塊備份都部署到同一個rack上。如果NameNode偵測到某數據塊的所有備份都在一個rack上,那么它就會把這個數據塊當做是mis-replicated(誤備份),然后它就會用上面所提到的策略,在其他的rack上把這個數據塊再備份一次。在NameNode收到異地rack備份成功后,該數據塊就成為了“備份數量高于所需備份數”狀態。此時NameNode會根據策略把本地的一個備份刪除,因為策略規定不能減少rack的數量。
**8.3.4 均衡器**
HDFS數據塊部署策略并不負責DaraNode的負載均衡。這是為了避免把新的(多半是用于備份)的數據放到很少一部分擁有大量閑置存儲空間的DataNode上。因此數據不一定會被負載合理地存放在各個DataNode中。在新的節點加入到集群中的時候,也會造成負載不均衡的情況。
均衡器就是一個用來在HDFS集群上平衡磁盤使用情況的工具。它采用一個閾值作為輸入參數,其范圍在0到1之間。如果某些節點的存儲利用率(也就是該節點磁盤存儲利用率)不同于整個集群的存儲利用率(也就是整個集群的總的存儲利用率),那么當類節點占集群總節點數的比例小于閾值時,集群便是負載均衡的。
這個工具是以一個應用程序的形式存在的,它可以根據管理員的要求運行。它能夠不斷地從高利用率的DataNode中把數據備份移動到低利用率的DataNode中。負載均衡的一個重要因素是要保證維持數據的可用性。當決定把一個數據備份移動到某個目標時,負載均衡器需要保證其決定不會降低這個數據備份所覆蓋的rack的數目。
均衡器可以通過最小化rack內數據拷貝的方式來優化均衡過程。如果均衡器決定將數據塊備份A移動到不同rack上的節點里,而湊巧此時該節點本身就包含了同一數據塊的另一個備份B,這時均衡器就會直接在目標節點上拷貝一份B,用來代替A。
均衡器操作的帶寬可以通過一個配置參數進行限制。允許使用的帶寬越高,達到均衡狀態的時間就越短,但是其與正常的業務進程的競爭沖突也會越大。
**8.3.5 數據塊掃描器**
每個DataNode都會定期運行掃描器,來掃描其數據塊備份,并驗證存貯的checksum是否與數據塊匹配。在每個掃描周期內,掃描器會調整讀取帶寬,以確保在規定的時間內完成校驗工作。如果客戶端讀完了一個數據塊,并且驗證checksum成功,它會發一個信息給DataNode. DataNode將其當做對數據塊備份的一次驗證。
每個數據塊的驗證時間會被存儲到一個可閱讀的日志文件里。無論何時,在DataNode的頂級目錄里,至少應該包含兩個文件:當前日志文件和以前的日志文件。每做一次驗證,新的驗證時間記錄就會被追繳到當前的日志文件中。相應的,每個DataNode的內存中也有一個掃描列表,并且按照驗證的時間進行排序。
當客戶端或者塊掃描器偵測到了一個損壞的塊數據時,就會通知NameNode. NameNode會標記相應的塊數據為“已損壞”,但是不會立刻安排刪除損壞的數據,而是開始進行數據拷貝。當且僅當正常的數據塊拷貝超過了其備份因子的時候,損壞的數據才會被安排移除。這個策略主要是為了盡可能長時間地保存數據。即使一個數據塊的所有備份都壞了,采用這個策略能讓用戶有機會從損壞的數據中回復盡可能多有用數據。
**8.3.6 停用**
集群管理員可以指定一個列表,列出需要停用的節點。一旦某個DataNode節點被標記為停用,它就不會再被選作可以部署的目標節點,但是該節點依然處于可讀狀態。NameNode開始規劃將該節點上的數據塊備份到其他的DataNodeshang . 一旦NameNode偵測到所有數據塊都已完成備份后,該節點就進入到停用狀態。 此時該節點就可以安全地從集群里移除了,而不會對數據的可用性造成影響。
**8.3.7 集群內數據拷貝**
當與超大數據集協同工作時,將數據導入/導出到HDFS集群里是一個令人畏懼的工作。HDFS提供了一個工具叫做DistCp, 用于大型集群內(局域網內)數據的并行拷貝。它是一個MapReduce任務,每一個Map任務拷貝元數據的一部分到目標文件系統。MapReduce框架會自動處理并行任務的分配,錯誤偵測和恢復工作。
**8.4\. Yahoo!的實踐**
Yahoo! 所采用的大型的HDFS集群共包含4000個節點。集群中的一個典型的節點包含兩個四核Xeon處理器,單核的主頻達到2.5 GHz,4-12個直連的SATA硬盤(其中每個容量為2TB), 24G物理內存,以及1G帶寬的以太網網卡。整個系統中70%的磁盤空間都是由HDFS進行分配,剩余的存儲空間則被預留為操作系統(Red Hat Linux),日志以及Map任務所使用(MapReduce所產生的中間數據不會存放在HDFS里)。
一個Rack上有40節點主機,它們共享一個IP交換機。Rack交換機之間通過8個核心交換機相互聯系。核心交換機還提供了這些Rack同集群外資源的通信能力。對于每個集群來,其NameNode和BackupNode主機都配有多達64GB的物理內存,不過客戶端應用程序的任務不會由這些主機來處理。總的來說,一個擁有4000節點的集群有11 PB(petabyte; 1000 terabytes)的存儲能力,如果數據塊采用三次冗余復制的策略,則集群可以為應用程序提供多達3.7 PB的分布式數據存儲空間。 HDFS投入使用的幾年以來,作為集群節點使用的主機也隨著技術的發展而不斷更新換代。新的集群節點主機會配置更強大的處理器,更大的磁盤存儲空間和物理內存。那些相對速度較慢,存儲容量較小的節點主機則逐步退役,或者被降級為集群備用主機,用以作為Hadoop的開發測試機。 以這個大型的集群(4000節點)為例,集群中有大約6500000個文件和8000000個數據塊。每個數據塊通常都有3個拷貝副本,每個數據節點會負責處理60000個數據節點的備份。每天,用戶應用程序會在集群上創建多達2000000的新文件。Yahoo!所使用的Hadoop集群的4000個節點可提供40 PB的在線數據存儲容量。
作為Yahoo!技術設施的重要組成部分,HDFS需要處理PB級別的公司企業數據。這就意味著處理HDFS的技術問題,其策略與處理研究項目中的問題完全不同。首當其沖的是要保證系統數據的健壯性和耐久性,其次還要兼顧到系統的運行效率,然后還要對用戶資源共享進行配額,并考慮系統管理員操作的方便性。
**8.4.1 數據耐久性**
通過將數據塊進行三次冗余備份,能夠有效地避免因為不相關節點失效而造成的數據丟失,從而增強數據的健壯性。不過,Yahoo! 倒是不太可能因為這樣的途徑丟失數據。對于這么大的集群來說,一年內丟失數據塊的幾率也不到千分之五。此外,對集群的一個關鍵共識是,每個月大約有0.8%的節點會發生故障。(就算是節點故障后能被恢復,但是一般來說不會去恢復節點里的數據。)所以,對于上述規模的集群來說,每天有1到2個節點發生故障并丟失數據是很正常的。不過,集群在2分鐘的時間內,就能重建發生故障的節點上的大約6000個數據塊,之所以重建的速度這么快,是因為集群采用并行的方式完成重建工作,而且集群的規模越大,并發的數目就越大,重建的效率也就越高。這樣一來,在兩分鐘之內,集群中的某些節點的所有數據塊徹底丟失(來不及重建)的概率就著實很小了。
節點的相關故障則是另一種完全不同的安全威脅。通常情況下最常發生的該類故障包括:Rack故障或者核心交換機故障。HDFS能夠做到對一個Rack交換機故障的容錯(因為每個數據塊在不同的Rack上都有備份)。但是如果一個核心交換機發生了故障,則會影響到集群中的多個Rack,這樣就有可能導致部分數據塊完全無法獲取。另一種可能造成節點相關故障的情況,是計劃外或計劃內的斷電。如果多個Rack同時斷電,很可能造成數據塊無效。而且即使是及時恢復了電力也不一定能恢復數據,因為大約有0.5%到1%的節點很可能在完全斷電后重啟的過程中失效。根據統計數據,在實際情況中,規模較大的集群在斷電重啟的過程中確實存在這部分節點數據丟失的情況。
除了全部節點失效的情況以外,存儲到硬盤的數據也可能發生損壞或者丟失。數據塊掃描器對所有的數據塊進行全盤掃描,每兩周進行一次,通過掃描結果可以發現,在整個過程正大約會發現20個損壞的數據塊副本。數據塊副本一旦發現損壞,就會被立即替換。
**8.4.2 共享HDFS的特性**
隨著HDFS使用的不斷普及,這個文件系統所面對的用戶越來越多,在資源共享方面的意義也越來越重要。共享HDFS的第一個特性,在于它的文件和目錄的權限構架與Unix系統的權限模式非常接近。在HDFS的權限框架中,文件和目錄對于不同的擁有者,以及相關用戶組其他成員,都有獨立的訪問權限。Unix(POSIX)和HDFS的主要區別,在于HDFS中的普通文件既沒有執行權限,也沒有sticky位(Unix中擁有sticky位的文件,只有其擁有者和root用戶才能進行刪除和重命名等操作——譯者注)。
在HDFS的早期版本中,用戶身份標識相對較弱:你的主機認為你是什么身份,你就是什么身份。當接入到HDFS時,應用程序客戶端僅僅是簡單地要求進行本次操作系統的用戶身份認證和用戶組身份認證。在新的權限框架中,應用程序客戶端必須登錄從可信的認證源獲取命名系統證書,然后才能登入。HDFS可以選用不同的證書認證管理技術,最初采用的是Kerberos(麻省理工學院開發的安全認證系統)。用戶程序可以使用相同的框架來確認命名系統也擁有可靠的身份。命名系統也可以從集群中的任何一個數據節點來申請證書。
HDFS總共的可用數據存儲空間是通過數據節點的個數,以及每個節點的存儲配比來決定的。HDFS早期的使用經驗證明,在用戶群中實施一些資源分配策略是非常有必要的。實施策略的目的不僅僅是要保證共享的公平性,還要考慮在多個用戶程序同時操作上千臺主機,進行寫數據操作時,如果才能確保應用程序不會將資源耗盡。對于HDFS來說,因為系統元數據總是存儲在RAM中,命名空間的大小(也就是文件和目錄的數量)是有限資源。為了管理存儲和命名空間資源,目錄的配額大小應當由命名空間的子樹中文件所占用的大小總和來決定,其中命名空間應當以該目錄為起始。當然,也可以為文件的總數和子樹中的目錄設定單獨的配額。
如果說HDFS的架構的主要目的在于應付應用程序產生的大數據集輸入,MapReduce程序框架則趨向于生成大量小的輸出文件(每個文件對應一個reduce任務),這些文件會給命名空間資源帶來壓力。通常情況下,一個目錄子樹會被壓縮到一個單獨的Hadoop歸檔文件中(HAR),以節約存儲空間。HAR(Hadoop歸檔文件)類似于常見的tar,JAR,或者Zip文件,HDFS能夠隨時訪問到壓縮歸檔文件中的某個文件,因此,HAR文件可以在MapReduce作業中作為輸入文件透明使用。
**8.4.3 擴展性和HDFS聯盟**
NameNode的擴展性曾經是一個關鍵的難題[Shv10]。因為NameNode將所有的命名空間和數據塊的位置存儲在了其內存當中,這樣一來,NameNode的堆內存就限制了文件的數量,同時也限制了數據塊的地址存放的數量。同理,這也限制了整個集群的存儲容量,因為集群需要NameNode的支持才能工作。HDFS鼓勵用戶盡量使用大文件,來減少NameNode的開支,但是在實際應用中,由于應用程序需求不斷地變化,這個辦法并不奏效。此外,我們發現很多新的HDFS應用程序就是需要存儲大量的小文件。我們通過設定配額管理,以及提供歸檔工具來進行調整,但是這些都沒有能夠從根本上解決擴展性的問題。
改進后的HDFS增加了一個新的特性,允許多個獨立命名空間(以及NameNode)共享集群中的物理存儲。命名空間可使用數據塊池(Block Pool)中的數據塊。數據塊池類似于SAN(存儲區域網 Storage Area Network)存儲系統里的邏輯單元(LUNs),使用數據塊池的命名空間就仿佛文件系統中的“卷標”一樣。
使用這種方法,不但提高了擴展性,還同時具備了其他幾個優點:它可以將不同應用的命名空間分離開來,改善集群的整體可用性。數據塊池抽象允許其他服務通過不同的命名空間結構來使用數據塊存儲。我們正在計劃探尋其他的方式來實現擴展性,例如只把部分的命名空間存儲在內存里,以及真正實現分布式的NameNode。
應用程序往往連續使用一個命名空間。命名空間能夠被掛接(mount),從而形成統一的視圖。客戶端掛接表為此提供了有效的途徑,與服務器端掛接表相比,客戶端的掛接表避免了與中央掛接表進行遠程過程調用(RPC),它的容錯性也更好。將集群范圍的命名空間共享是最簡單的實現方法,把所有集群的客戶端指向同一個客戶端掛接表就可以實現。客戶端掛接表也允許應用程序創建私有的命名空間視圖。這個工作機制與進程獨占的命名空間相似,可以用來處理分布系統中的遠程調用。
**8.5 經驗**
Hadoop文件系統的開發團隊規模很小,卻成功地保障了系統的穩定性和健壯性,讓它能夠符合實際應用的要求。之所以能夠成功地做到這一點,很大程度上是鑒于HDFS擁有一套非常簡潔的構架:數據塊備份,定期數據塊報告和中央元數據服務器。簡化POSIX語義也很重要。盡管把整個元數據都保存在內存中會影響到命名空間的擴展性,但是卻保證了NameNode的簡潔性,有效地避免了一般分布式系統采用的復雜的鎖機制。Hadoop能夠成功的另一原因在于快速地將系統應用到了Yahoo!的產品當中,讓系統能夠快速地遞增改進。HDFS非常健壯,因為NameNode極少出問題;實際上,大多數的機器非運行時間是由于軟件升級造成的。直到最近,才有相應的故障備份方案(盡管是手動操作)加入到系統管理中來。
很多人對使用Java語言構建可擴展的數據庫系統表示非常驚訝。因為Java通常被認為消耗內存過大,垃圾回收效率太低,會影響到NameNode的擴展性。雖然上述的問題的確存在,不過Java對系統的健壯性也有顯著的貢獻,因為Java的語言機制成功回避了指針和內存管理bug可能導致的系統問題(C/C++語言在指針和內存管理上被認為非常難以維護——譯者注)。
**8.6 鳴謝**
我們感謝Yahoo!投資給Hadoop項目,并長期將其保持為開源項目。80%的HDFS和MapReduce代碼是在Yahoo!開發的。我們感謝所有的Hadoop貢獻者和合作者,你們為項目做出了很大的貢獻!
- 前言(卷一)
- 卷1:第1章 Asterisk
- 卷1:第3章 The Bourne-Again Shell
- 卷1:第5章 CMake
- 卷1:第6章 Eclipse之一
- 卷1:第6章 Eclipse之二
- 卷1:第6章 Eclipse之三
- 卷1:第8章 HDFS——Hadoop分布式文件系統之一
- 卷1:第8章 HDFS——Hadoop分布式文件系統之二
- 卷1:第8章 HDFS——Hadoop分布式文件系統
- 卷1:第12章 Mercurial
- 卷1:第13章 NoSQL生態系統
- 卷1:第14章 Python打包工具
- 卷1:第15章 Riak與Erlang/OTP
- 卷1:第16章 Selenium WebDriver
- 卷1:第18章 SnowFlock
- 卷1:第22章 Violet
- 卷1:第24章 VTK
- 卷1:第25章 韋諾之戰
- 卷2:第1章 可擴展Web架構與分布式系統之一
- 卷2:第1章 可擴展Web架構與分布式系統之二
- 卷2:第2章 Firefox發布工程
- 卷2:第3章 FreeRTOS
- 卷2:第4章 GDB
- 卷2:第5章 Glasgow Haskell編譯器
- 卷2:第6章 Git
- 卷2:第7章 GPSD
- 卷2:第9章 ITK
- 卷2:第11章 matplotlib
- 卷2:第12章 MediaWiki之一
- 卷2:第12章 MediaWiki之二
- 卷2:第13章 Moodle
- 卷2:第14章 NginX
- 卷2:第15章 Open MPI
- 卷2:第18章 Puppet part 1
- 卷2:第18章 Puppet part 2
- 卷2:第19章 PyPy
- 卷2:第20章 SQLAlchemy
- 卷2:第21章 Twisted
- 卷2:第22章 Yesod
- 卷2:第24章 ZeroMQ