思考問題
HDFS的架構
- 設計前提和目標
- 專為存儲超大文件而設計:hdfs應該能夠支持GB級別大小的文件;它應該能夠提供很大的數據帶寬并且能夠在集群中拓展到成百上千個節點;它的一個實例應該能夠支持千萬數量級別的文件。
- 適用于流式的數據訪問:hdfs適用于批處理的情況而不是交互式處理;它的重點是保證高吞吐量而不是低延遲的用戶響應
- 容錯性:完善的冗余備份機制
- 支持簡單的一致性模型:HDFS需要支持一次寫入多次讀取的模型,而且寫入過程文件不會經常變化
- 移動計算優于移動數據:HDFS提供了使應用計算移動到離它最近數據位置的接口
- 兼容各種硬件和軟件平臺
- 不適合的場景
- 大量的小文件:文件的元數據都存儲在NameNode內存中,大量小文件會占用大量內存。
- 低延遲數據訪問:hdfs是專門針對高數據吞吐量而設計的,無法保證低延遲的要求
- 多用戶寫入,任意修改文件:hdfs采取一次寫入,多次讀寫的策略,不支持修改,這樣才能保證高數據吞吐量
- HDFS架構設計
- HDFS主要由3個組件構成,分別是NameNode、SecondaryNameNode和DataNode,HSFS是以master/slave模式運行的,其中NameNode、SecondaryNameNode 通常運行在master節點,DataNode運行slave節點。
數據塊
- 磁盤數據塊是磁盤讀寫的基本單位,與普通文件系統類似,hdfs也會把文件分塊來存儲。
- hdfs默認數據塊大小為64MB,磁盤塊一般為512B,hdfs塊為何如此之大呢?塊增大可以減少尋址時間與文件傳輸時間的比例,若尋址時間為10ms,磁盤傳輸速率為100MB/s,那么尋址與傳輸比僅為1%。
- 當然,磁盤塊太大也不好,因為一個MapReduce通常以一個塊作為輸入,塊過大會導致整體任務數量過小,降低作業處理速度。
- 數據塊是存儲在DataNode中的,為了能夠容錯數據塊是以多個副本的形式分布在集群中的,副本數量默認為3,后面會專門介紹數據塊的復制機制。
hdfs按塊存儲還有如下好處:
- 文件可以任意大,也不用擔心單個結點磁盤容量小于文件的情況
- 簡化了文件子系統的設計,子系統只存儲文件塊數據,而文件元數據則交由其它系統(NameNode)管理
- 有利于備份和提高系統可用性,因為可以以塊為單位進行備份,hdfs默認備份數量為3。
- 有利于負載均衡
DataNode
- DataNode是hdfs中的worker節點,它負責存儲數據塊,也負責為系統客戶端提供數據塊的讀寫服務,同時還會根據NameNode的指示來進行創建、刪除、和復制等操作
- 此外,它還會通過心跳定期向NameNode發送所存儲文件塊列表信息。
- 當對hdfs文件系統進行讀寫時,NameNode告知客戶端每個數據駐留在哪個DataNode,客戶端直接與DataNode進行通信,DataNode還會與其它DataNode通信,復制這些塊以實現冗余。
NameNode和DataNode架構圖
SecondaryNameNode
- SecondaryNameNode并不是NameNode的備份。所有HDFS文件的元信息都保存在NameNode的內存中。在NameNode啟動時,它首先會加載fsimage到內存中,在系統運行期間,所有對NameNode的操作也都保存在了內存中,同時為了防止數據丟失,這些操作又會不斷被持久化到本地edits文件中。
- Edits文件存在的目的是為了提高系統的操作效率,NameNode在更新內存中的元信息之前都會先將操作寫入edits文件。在NameNode重啟的過程中,edits會和fsimage合并到一起,但是合并的過程會影響到Hadoop重啟的速度,SecondaryNameNode就是為了解決這個問題而誕生的。
SecondaryNameNode的角色就是定期的合并edits和fsimage文件,我們來看一下合并的步驟:
- 合并之前告知NameNode把所有的操作寫到新的edites文件并將其命名為edits.new。
- SecondaryNameNode從NameNode請求fsimage和edits文件
- SecondaryNameNode把fsimage和edits文件合并成新的fsimage文件
- NameNode從SecondaryNameNode獲取合并好的新的fsimage并將舊的替換掉,并把edits用第一步創建的edits.new文件替換掉
- 更新fstime文件中的檢查點
最后再總結一下整個過程中涉及到NameNode中的相關文件
- fsimage :保存的是上個檢查點的HDFS的元信息
- edits :保存的是從上個檢查點開始發生的HDFS元信息狀態改變信息
- fstime:保存了最后一個檢查點的時間戳
數據備份
HDFS通過備份數據塊的形式來實現容錯,除了文件的最后一個數據塊外,其它所有數據塊大小都是一樣的。另外數據塊的大小和備份因子都是可以配置的。
NameNode負責各個數據塊的備份,DataNode會通過心跳的方式定期的向NameNode發送自己節點上的Block 報告,這個報告中包含了DataNode節點上的所有數據塊的列表。
文件副本的分布位置直接影響著HDFS的可靠性和性能。
- 一個大型的HDFS文件系統一般都是需要跨很多機架的,不同機架之間的數據傳輸需要經過網關,并且,同一個機架中機器之間的帶寬要大于不同機架機器之間的帶寬。
- 如果把所有的副本都放在不同的機架中,這樣既可以防止機架失敗導致數據塊不可用,又可以在讀數據時利用到多個機架的帶寬,并且也可以很容易的實現負載均衡。
- 但是,如果是寫數據,各個數據塊需要同步到不同的機架,會影響到寫數據的效率。
- 而在Hadoop中,如果副本數量是3的情況下,Hadoop默認是這么存放的,把第一個副本放到機架的一個節點上,另一個副本放到同一個機架的另一個節點上,把最后一個節點放到不同的機架上。這種策略減少了跨機架副本的個數提高了寫的性能,也能夠允許一個機架失敗的情況,算是一個很好的權衡。
- 關于副本的選擇,在讀的過程中,HDFS會選擇最近的一個副本給請求者。
安全模式
- 當 Hadoop的NameNode節點啟動時,會進入安全模式階段。在此階段,DataNode會向NameNode上傳它們數據塊的列表,讓 NameNode得到塊的位置信息,并對每個文件對應的數據塊副本進行統計。當最小副本條件滿足時,即一定比例的數據塊都達到最小副本數,系統就會退出安全模式,而這需要一定的延遲時間。當最小副本條件未達到要求時,就會對副本數不足的數據塊安排DataNode進行復制,直至達到最小副本數。
- 而在安全模式下,系統會處于只讀狀態,NameNode不會處理任何塊的復制和刪除命令。
- $ hdfs dfsadmin -safemode leave 退出安全模式
HDFS中的溝通協議
- 所有的HDFS中的溝通協議都是基于tcp/ip協議,一個客戶端通過指定的tcp端口與NameNode機器建立連接,并通過ClientProtocol協議與NameNode交互。
- 而DataNode則通過DataNode Protocol協議與NameNode進行溝通。
- HDFS的RCP(遠程過程調用)對ClientProtocol和DataNode Protocol做了封裝。按照HDFS的設計,NameNode不會主動發起任何請求,只會被動接受來自客戶端或DataNode的請求。
可靠性保證
- 可以允許DataNode失敗。
- DataNode會定期(默認3秒)的向NameNode發送心跳,若NameNode在指定時間間隔內沒有收到心跳,它就認為此節點已經失敗。
- 此時,NameNode把失敗節點的數據(從另外的副本節點獲取)備份到另外一個健康的節點。
- 這保證了集群始終維持指定的副本數。
- 可以檢測到數據塊損壞。
- 在讀取數據塊時,HDFS會對數據塊和保存的校驗和文件匹配,如果發現不匹配,NameNode同樣會重新備份損壞的數據塊。
hdfs文件讀寫過程剖析
了解客戶端與NameNode和DataNode的交互過程十分重要,有助于加深我們對hdfs架構設計的理解。
hdfs文件讀取過程
- dfs有一個FileSystem實例,客戶端通過調用這個實例的open()方法就可以打開系統中希望讀取的文件。
- hdfs通過rpc調用NameNode獲取文件塊的位置信息,對于文件的每一個塊,NameNode會返回含有該塊副本的DataNode的節點地址。
- 另外,客戶端還會根據網絡拓撲來確定它與每一個DataNode的位置信息,從離它最近的那個DataNode獲取數據塊的副本,最理想的情況是數據塊就存儲在客戶端所在的節點上。
hdfs會返回一個FSDataInputStream對象,FSDataInputStream類轉而封裝成DFSDataInputStream對象,這個對象管理著與DataNode和NameNode的I/O,具體過程是:
- 客戶端發起讀請求
- 客戶端與NameNode得到文件的塊及位置信息列表
- 客戶端直接和DataNode交互讀取數據
- 讀取完成關閉連接
當FSDataInputStream與DataNode通信時遇到錯誤,它會選取另一個較近的DataNode,并為出故障的DataNode做標記以免重復向其讀取數據。FSDataInputStream還會對讀取的數據塊進行校驗和確認,發現塊損壞時也會重新讀取并通知NameNode。
這樣設計的巧妙之處:
- 讓客戶端直接聯系DataNode檢索數據,可以使hdfs擴展到大量的并發客戶端,因為數據流就是分散在集群的每個節點上的,在運行MapReduce任務時,每個客戶端就是一個DataNode節點。
- NameNode僅需相應塊的位置信息請求(位置信息在內存中,速度極快),否則隨著客戶端的增加,NameNode會很快成為瓶頸。
關于hadoop與網絡拓撲
在海量數據處理過程中,主要限制因素是節點之間的帶寬。衡量兩個節點之間的帶寬往往很難實現,在這里hadoop采取了一個簡單的方法,它把網絡拓撲看成是一棵樹,兩個節點的距離=它們到最近共同祖先距離的總和,而樹的層次可以這么劃分:
- 同一節點中的進程
- 同一機架上的不同節點
- 同一數據中心不同機架
- 不同數據中心的節點
若數據中心d1中一個機架r1中一個節點n1表示為d1/r1/n1,則:
distance(d1/r1/n1,d1/r1/n1)=0;
distance(d1/r1/n1,d1/r1/n2)=2;
distance(d1/r1/n1,d1/r2/n3)=4;
distance(d1/r1/n1,d2/r3/n4)=6;
hdfs文件寫入過程
- hdfs有一個DistributedFileSystem實例,客戶端通過調用這個實例的create()方法就可以創建文件。
- DistributedFileSystem會發送給NameNode一個RPC調用,在文件系統的命名空間創建一個新文件,在創建文件前NameNode會做一些檢查,如文件是否存在,客戶端是否有創建權限等,若檢查通過,NameNode會為創建文件寫一條記錄到本地磁盤的EditLog,若不通過會向客戶端拋出IOException。
- 創建成功之后DistributedFileSystem會返回一個FSDataOutputStream對象,客戶端由此開始寫入數據。
同讀文件過程一樣,FSDataOutputStream類轉而封裝成DFSDataOutputStream對象,這個對象管理著與DataNode和NameNode的I/O,具體過程是:
- 客戶端在向NameNode請求之前先寫入文件數據到本地文件系統的一個臨時文件
- 待臨時文件達到塊大小時開始向NameNode請求DataNode信息
- NameNode在文件系統中創建文件并返回給客戶端一個數據塊及其對應DataNode的地址列表(列表中包含副本存放的地址)
- 客戶端通過上一步得到的信息把創建臨時文件塊flush到列表中的第一個DataNode
- 當文件關閉,NameNode會提交這次文件創建,此時,文件在文件系統中可見
上面第四步描述的flush過程實際處理過程比較負雜,現在單獨描述一下:
- 首先,第一個DataNode是以數據包(數據包一般4KB)的形式從客戶端接收數據的,DataNode在把數據包寫入到本地磁盤的同時會向第二個DataNode(作為副本節點)傳送數據。
- 在第二個DataNode把接收到的數據包寫入本地磁盤時會向第三個DataNode發送數據包
- 第三個DataNode開始向本地磁盤寫入數據包。此時,數據包以流水線的形式被寫入和備份到所有DataNode節點
- 傳送管道中的每個DataNode節點在收到數據后都會向前面那個DataNode發送一個ACK,最終,第一個DataNode會向客戶端發回一個ACK
- 當客戶端收到數據塊的確認之后,數據塊被認為已經持久化到所有節點。然后,客戶端會向NameNode發送一個確認
- 如果管道中的任何一個DataNode失敗,管道會被關閉。數據將會繼續寫到剩余的DataNode中。同時NameNode會被告知待備份狀態,NameNode會繼續備份數據到新的可用的節點
- 數據塊都會通過計算校驗和來檢測數據的完整性,校驗和以隱藏文件的形式被單獨存放在hdfs中,供讀取時進行完整性校驗
hdfs文件刪除過程
hdfs文件刪除過程一般需要如下幾步:
- 一開始刪除文件,NameNode只是重命名被刪除的文件到/trash目錄,因為重命名操作只是元信息的變動,所以整個過程非常快。在/trash中文件會被保留一定間隔的時間(可配置,默認是6小時),在這期間,文件可以很容易的恢復,恢復只需要將文件從/trash移出即可。
- 當指定的時間到達,NameNode將會把文件從命名空間中刪除
- 標記刪除的文件塊釋放空間,HDFS文件系統顯示空間增加