概述:hdfs即hadoop distributed file system分布式檔案系統,它的設計目标是把超大資料集存儲到分布在網絡中的多台普通商用計算機上,并且能夠提供高可靠性和高吞吐量的服務。分布式檔案系統要比普通磁盤檔案系統複雜,因為它要引入網絡程式設計,分布式檔案系統要容忍節點故障也是一個很大的挑戰。
專為存儲超大檔案而設計:hdfs應該能夠支援gb級别大小的檔案;它應該能夠提供很大的資料帶寬并且能夠在叢集中拓展到成百上千個節點;它的一個執行個體應該能夠支援千萬數量級别的檔案。
适用于流式的資料通路:hdfs适用于批處理的情況而不是互動式處理;它的重點是保證高吞吐量而不是低延遲的使用者響應
容錯性:完善的備援備份機制
支援簡單的一緻性模型:hdfs需要支援一次寫入多次讀取的模型,而且寫入過程檔案不會經常變化
移動計算優于移動資料:hdfs提供了使應用計算移動到離它最近資料位置的接口
相容各種硬體和軟體平台
大量小檔案:檔案的中繼資料都存儲在namenode記憶體中,大量小檔案會占用大量記憶體。
低延遲資料通路: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進行互動,而這些資訊的維護者就是namenode。
namenode管理着檔案系統命名空間,它維護這檔案系統樹及樹中的所有檔案和目錄。namenode也負責維護所有這些檔案或目錄的打開、關閉、移動、重命名等操作。對于實際檔案資料的儲存與操作,都是由datanode負責。當一個用戶端請求資料時,它僅僅是從namenode中擷取檔案的元資訊,而具體的資料傳輸不需要經過namenode,是由用戶端直接與相應的datanode進行互動。
namenode儲存元資訊的種類有:
檔案名目錄名及它們之間的層級關系
檔案目錄的所有者及其權限
每個檔案塊的名及檔案有哪些塊組成
需要注意的是,namenode元資訊并不包含每個塊的位置資訊,這些資訊會在namenode啟動時從各個datanode擷取并儲存在記憶體中,因為這些資訊會在系統啟動時由資料節點重建。把塊位置資訊放在記憶體中,在讀取資料時會減少查詢時間,增加讀取效率。namenode也會實時通過心跳機制和datanode進行互動,實時檢查檔案系統是否運作正常。不過namenode元資訊會儲存各個塊的名稱及檔案由哪些塊組成。
一般來說,一條元資訊記錄會占用200byte記憶體空間。假設塊大小為64mb,備份數量是3 ,那麼一個1gb大小的檔案将占用16*3=48個檔案塊。如果現在有1000個1mb大小的檔案,則會占用1000*3=3000個檔案塊(多個檔案不能放到一個塊中)。我們可以發現,如果檔案越小,存儲同等大小檔案所需要的元資訊就越多,是以,hadoop更喜歡大檔案。
在namenode中存放元資訊的檔案是 fsimage。在系統運作期間所有對元資訊的操作都儲存在記憶體中并被持久化到另一個檔案edits中。并且edits檔案和fsimage檔案會被secondarynamenode周期性的合并(合并過程會在secondarynamenode中詳細介紹)。
運作namenode會占用大量記憶體和i/o資源,一般namenode不會存儲使用者資料或執行mapreduce任務。
為了簡化系統的設計,hadoop隻有一個namenode,這也就導緻了hadoop叢集的單點故障問題。是以,對namenode節點的容錯尤其重要,hadoop提供了如下兩種機制來解決:
将hadoop中繼資料寫入到本地檔案系統的同時再實時同步到一個遠端挂載的網絡檔案系統(nfs)。
運作一個secondary namenode,它的作用是與namenode進行互動,定期通過編輯日志檔案合并命名空間鏡像,當namenode發生故障時它會通過自己合并的命名空間鏡像副本來恢複。需要注意的是secondarynamenode儲存的狀态總是滞後于namenode,是以這種方式難免會導緻丢失部分資料(後面會詳細介紹)。
datanode是hdfs中的worker節點,它負責存儲資料塊,也負責為系統用戶端提供資料塊的讀寫服務,同時還會根據namenode的訓示來進行建立、删除、和複制等操作。此外,它還會通過心跳定期向namenode發送所存儲檔案塊清單資訊。當對hdfs檔案系統進行讀寫時,namenode告知用戶端每個資料駐留在哪個datanode,用戶端直接與datanode進行通信,datanode還會與其它datanode通信,複制這些塊以實作備援。
namenode和datanode架構圖

需要注意,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中的溝通協定都是基于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同樣會重新備份損壞的資料塊。
了解用戶端與namenode和datanode的互動過程十分重要,有助于加深我們對hdfs架構設計的了解。
hdfs有一個filesystem執行個體,用戶端通過調用這個執行個體的open()方法就可以打開系統中希望讀取的檔案。hdfs通過rpc調用namenode擷取檔案塊的位置資訊,對于檔案的每一個塊,namenode會傳回含有該塊副本的datanode的節點位址,另外,用戶端還會根據網絡拓撲來确定它與每一個datanode的位置資訊,從離它最近的那個datanode擷取資料塊的副本,最理想的情況是資料塊就存儲在用戶端所在的節點上。
hdfs會傳回一個fsdatainputstream對象,fsdatainputstream類轉而封裝成dfsdatainputstream對象,這個對象管理着與datanode和namenode的i/o,具體過程是:
當fsdatainputstream與datanode通信時遇到錯誤,它會選取另一個較近的datanode,并為出故障的datanode做标記以免重複向其讀取資料。fsdatainputstream還會對讀取的資料塊進行校驗和确認,發現塊損壞時也會重新讀取并通知namenode。
這樣設計的巧妙之處:
讓用戶端直接聯系datanode檢索資料,可以使hdfs擴充到大量的并發用戶端,因為資料流就是分散在叢集的每個節點上的,在運作mapreduce任務時,每個用戶端就是一個datanode節點。
namenode僅需相應塊的位置資訊請求(位置資訊在記憶體中,速度極快),否則随着用戶端的增加,namenode會很快成為瓶頸。
在海量資料處理過程中,主要限制因素是節點之間的帶寬。衡量兩個節點之間的帶寬往往很難實作,在這裡hadoop采取了一個簡單的方法,它把網絡拓撲看成是一棵樹,連個節點的距離=它們到最近共同祖先距離的總和,而樹的層次可以這麼劃分:
同一節點中的程序
同一機架上的不同節點
同一資料中心不同機架
不同資料中心的節點
若資料中心d1中一個機架r1中一個節點n1表示為d1/r1/n1,則:
hdfs有一個distributedfilesystem執行個體,用戶端通過調用這個執行個體的create()方法就可以建立檔案。distributedfilesystem會發送給namenode一個rpc調用,在檔案系統的命名空間建立一個新檔案,在建立檔案前namenode會做一些檢查,如檔案是否存在,用戶端是否有建立權限等,若檢查通過,namenode會為建立檔案寫一條記錄到本地磁盤的editlog,若不通過會向用戶端抛出ioexception。建立成功之後distributedfilesystem會傳回一個fsdataoutputstream對象,用戶端由此開始寫入資料。
同讀檔案過程一樣,fsdataoutputstream類轉而封裝成dfsdataoutputstream對象,這個對象管理着與datanode和namenode的i/o,具體過程是:
上面第四步描述的flush過程實際處理過程比較負雜,現在單獨描述一下:
hdfs檔案删除過程一般需要如下幾步: