【轉(zhuǎn)】HDFS介紹
HDFS介紹

Trafalgar
關(guān)注他
5 人贊同了該文章
HDFS是Hadoop使用的分布式文件系統(tǒng),能存儲和處理大規(guī)模數(shù)據(jù)。HDFS的設(shè)計(jì)目標(biāo)是在標(biāo)準(zhǔn)硬件上運(yùn)行,從而提供高容錯性,并且能夠處理已存儲的大量數(shù)據(jù)。
使用場景
首先需要明確的是,所有的存儲都是為計(jì)算服務(wù)的。
計(jì)算任務(wù)根據(jù)其實(shí)時性可以分為兩種,在線計(jì)算和離線計(jì)算。
在線計(jì)算的實(shí)時性要求比較強(qiáng),所以對存儲的時延要求高,往往會存儲在MySQL之類的TP數(shù)據(jù)庫中。
離線計(jì)算對實(shí)時性要求比較低,所以對存儲的時延要求低,一般只要求能正常讀寫就可以了,會更加關(guān)注存儲的成本。這一類數(shù)據(jù)會選擇存儲在HDFS上。
HDFS通常用于處理離線數(shù)據(jù)的存儲和分析,例如Web日志數(shù)據(jù)或者機(jī)器學(xué)習(xí)訓(xùn)練數(shù)據(jù)。另外,由于HDFS天生就是Hadoop生態(tài)系統(tǒng)的存儲底座,它也可以作為Hadoop生態(tài)系統(tǒng)中其他工具的基礎(chǔ),例如MapReduce和Spark,所以可以支持的上層計(jì)算引擎非常豐富。
總體架構(gòu)
HDFS在編寫的時候主要是參考GFS,所以架構(gòu)比較經(jīng)典。主要分為Client,Namenode和Datanode三個組件。Namenode的HA使用主從備份方案,Datanode使用三副本的鏈?zhǔn)綇?fù)制。
HDFS集群由一個NameNode和多個DataNode組成。NameNode負(fù)責(zé)管理文件系統(tǒng)的命名空間(Namespace),存儲文件的元數(shù)據(jù)信息(如文件名、文件路徑、文件長度、文件塊列表等),以及每個文件塊所在的DataNode節(jié)點(diǎn)。DataNode負(fù)責(zé)存儲實(shí)際的數(shù)據(jù)塊(Block),并定期向NameNode匯報自己所存儲的數(shù)據(jù)塊列表。
HDFS分離元數(shù)據(jù)和實(shí)際數(shù)據(jù)的設(shè)計(jì),使得NameNode可以專注于管理文件系統(tǒng)的命名空間,Datanode只負(fù)責(zé)數(shù)據(jù)存儲。理論上可以通過橫向拓展實(shí)現(xiàn)無限存儲。
客戶端
DFSClient是所有客戶端底層調(diào)用的對象。
讀數(shù)據(jù)
底層調(diào)用DFSClient.open()打開文件,并構(gòu)造文件輸入流。構(gòu)建文件流時,需要調(diào)用openInfo找Namenode拿到該文件對應(yīng)的Block信息。再從Datanode拿到最后一個Block的長度信息。
讀分為以下三種,依次速度增加。在進(jìn)行讀取時,會優(yōu)先使用最快的
網(wǎng)絡(luò)讀會根據(jù)當(dāng)前offset從Namenode找到對應(yīng)Block存儲的Datanode,從中選出一個最近的,然后構(gòu)建BlockReader對象。如果使用BlockReader讀取校驗(yàn)失敗,會匯報給Namenode。
BlockReader會根據(jù)參數(shù)生成網(wǎng)絡(luò)讀或者短路讀的Reader。RemoteBlockReader2用于網(wǎng)絡(luò)讀,會使用PacketReceiver.receiveNextPacket()來讀取Packet,就是根據(jù)頭部讀出數(shù)據(jù),并進(jìn)行校驗(yàn)
BlockReaderLocal用于進(jìn)行本地短路讀,會使用Domain Socket來進(jìn)行通信,還使用了雙buffer的策略,保證每次都能完整地進(jìn)行校驗(yàn)。
tryReadZeroCopy()是用于進(jìn)行零拷貝讀的方法,底層是調(diào)用linux的系統(tǒng)調(diào)用。如果讀取失敗,會fallback到上面兩種讀取。
BlockReaderLocal用于進(jìn)行本地短路讀,會使用Domain Socket來進(jìn)行通信,還使用了雙buffer的策略,保證每次都能完整地進(jìn)行校驗(yàn)。
tryReadZeroCopy()是用于進(jìn)行零拷貝讀的方法,底層是調(diào)用linux的系統(tǒng)調(diào)用。如果讀取失敗,會fallback到上面兩種讀取。
3.文件短路讀操作
同一個物理機(jī)上的Client和Datanode可以通過Domain Socket,使用共享內(nèi)存進(jìn)行數(shù)據(jù)傳輸。兩者通過DataTransferProtocol約定協(xié)商Slot對象,Slot在共享內(nèi)存中分配,就是一個Slot數(shù)組,是管理內(nèi)存的基本單元。結(jié)構(gòu)如下。
流程是先通過requestShortCircuitShm()申請供銷內(nèi)存,創(chuàng)建完畢后,客戶端創(chuàng)建Slot,并調(diào)用requestShortCircuitFds()向Datanode發(fā)起打開文件的請求,并通過Domain Socket來傳輸文件描述符。DFSClient 的 BlockReaderFactory對象成功地從 domainSocket 接收了數(shù)據(jù)塊文件和校驗(yàn)和文件的文件描述符之后,就可以初始化數(shù)據(jù)塊文件和校驗(yàn)和文件的輸入流并構(gòu)造ShortCircuitReplica對象了,然后通過文件流來讀取文件。Datanode會根據(jù)客戶端的Slot創(chuàng)建自己的Slot。
Slot的Anchor是一個整型,表示該塊內(nèi)存無需驗(yàn)證,同時記錄了該內(nèi)存的引用計(jì)數(shù)。
在客戶端,會為每個Datanode維護(hù)一個DfsClientShmManager,這個Manager里面維護(hù)了兩個DfsClientShm隊(duì)列,一個是內(nèi)存已滿的,一個是內(nèi)存未滿的。ShortCircuitReplica類封裝了一個短路讀數(shù)據(jù)塊副本的所有信息。ShortCircuitCache則維護(hù)了負(fù)責(zé)對 DFSClient 的所有ShortCircuitReplica 對象進(jìn)行緩存以及生命周期管理等操作。其周期轉(zhuǎn)化如下圖所示。
在Datanode端,維護(hù)了一個ShortCircuitRegistry來管理所有的共享內(nèi)存,共享內(nèi)存的抽象是RegisteredShm。
3.文件寫操作
對于create寫,需要從Namenode拿到文件狀態(tài),然后計(jì)算這次寫的packet大小,一般是65532B。如果有錯誤,則將ackQueue中所有的Packet都重新發(fā)送。所有寫入完畢后,發(fā)送空包,表示結(jié)束。其發(fā)送流程如下圖所示。
寫入時,先使用Packet對象緩存數(shù)據(jù)。Packet寫滿時,將其放入dataQueue,等待DataStreamer取出發(fā)送。有可能最后一個chunk寫不滿,需要對其進(jìn)行paddingf。寫入流程如下圖所示。
實(shí)際執(zhí)行寫入的是DataStreamer,會調(diào)用 nextBlockOutputStream()方法向 Namenode 申請 分配新的數(shù)據(jù)塊,然后根據(jù)數(shù)據(jù)塊信息構(gòu)建輸出流,底層是使用Sender對象。如果是最后一個包,需要等前面所有Packet都發(fā)送才能發(fā)送,并且需要等待其完成。
Append寫也是類似,只是需要打開最后一個Block的流。如果最后一個Block已滿,則按照正常邏輯打開。
Namenode
1.文件系統(tǒng)目錄樹
INode相關(guān)
INode有兩個關(guān)鍵子類,INodeDirectory和INodeFile。
INode的成員變量包含了POSIX標(biāo)準(zhǔn)的七個屬性
INodeDirectory和INodeFile還實(shí)現(xiàn)了Permission屬性和Feature屬性,F(xiàn)eature屬性中包含了磁盤配額、正在構(gòu)建(UnderConstrution)、快照(Snapshot)等HDFS額外提供的功能。
為了支持在快照下剪切功能,引入了WithName,WithCount和DstReference三個類??煺罩械脑募⑹褂肳ithName代替,指向WithCount對象。剪切的目標(biāo)位置將指向DstReference對象,DstReference也將指向WithCount對象。WithCount對象將指向真正的文件。
Snapshot
快照支持對某個時刻的文件系統(tǒng)進(jìn)行只讀操作,該功能通過在Feature中添加DirectoryWithSnapshotFeature或者DirectorySnapshottableFeature來實(shí)現(xiàn)。
在DirectoryWithSnapshotFeature中,對每個快照將維護(hù)兩個list,CeateList和DeleteList,分別記錄在該快照以后,對快照內(nèi)的文件進(jìn)行刪改。
添加文件時,需要在CreateList中增加該文件,不會改動原目錄的結(jié)構(gòu)。
使用快照讀時,會從當(dāng)前快照向后查找,檢查dlist中是否有相關(guān)文件,如果有直接返回,如果一直沒有就去原目錄下讀。
讀取快照文件夾下的文件時,需要根據(jù)當(dāng)前狀態(tài),clist和dlist進(jìn)行逆推。
FSEditLog
分為五個狀態(tài),UNINITIALIZED最初試的狀態(tài),BETWEEN_LOG_SEGMENTS已經(jīng)打開但還未寫入數(shù)據(jù),IN_SEGMENT可以寫入數(shù)據(jù)的狀態(tài),CLOSED關(guān)閉,OPEN_FOR_READING備份節(jié)點(diǎn)初始化的狀態(tài)。
為了處理存儲在不同軟件上的情況,進(jìn)行了抽象,使用JournalSet進(jìn)行管理。
真正寫入數(shù)據(jù)時,使用雙Buffer進(jìn)行優(yōu)化,主備Buffer防止Block。其中,向buffer寫數(shù)據(jù)會進(jìn)行同步。由于正在寫的buffer會被標(biāo)記為isSyncRunning=true,真正進(jìn)行Flush的時候無需同步。
FSImage
很簡單的東西,用protobuf進(jìn)行序列化。
新記錄存到editlog,然后定期和fsimage進(jìn)行合并。
2.數(shù)據(jù)塊管理
Block
BlockInfo中的BlockCollection指向其屬于的文件,triplets是一個隱性的雙向鏈表,3i是自己,3i+1是同一個Datanode存儲的上一個BlockInfo對象,3*i+2是后一個。而如果保存了3個副本,那么第0,3,6則是三個副本所在的Datanode位置。
Namenode只存儲Block的信息,包括blockid,大小和時間戳。具體的Block和BlockInfo對應(yīng)的關(guān)系會根據(jù)Datanode的上報進(jìn)行動態(tài)更新。
BlockManager
具體下發(fā)復(fù)制指令時,會從needReplications隊(duì)列中選取一定數(shù)量的blocks一次性處理。這里一共設(shè)涉及三個問題,怎么選擇源節(jié)點(diǎn),目標(biāo)節(jié)點(diǎn)和什么時候執(zhí)行任務(wù)。源節(jié)點(diǎn)的選擇還是隨機(jī)的,但是會優(yōu)先選擇不在寫集合里面的節(jié)點(diǎn)。目標(biāo)節(jié)點(diǎn)的選擇遵循不同機(jī)架,不同Datanode的原則。執(zhí)行該任務(wù)是在Datanode上報信息時,在Response中攜帶該指令。
增加/刪除Block是通過INode的相關(guān)方法實(shí)現(xiàn)的,增加/刪除Replica是通過各種的操作實(shí)現(xiàn)的
Corruption是通過比較Datanode上報信息和Namenode信息來發(fā)現(xiàn)的。
所有副本數(shù)量的變動都會導(dǎo)致需要復(fù)制的副本數(shù)量變化。需要變多的,由needReplica記錄,并每隔一段時間轉(zhuǎn)發(fā)到pendingReplica中,在Datanode匯報時進(jìn)行下發(fā)。需要變少的,放到excessReplicateMap中,每隔一段時間轉(zhuǎn)發(fā)到invalidateBlocks中進(jìn)行下發(fā)。
數(shù)據(jù)塊的匯報由Datenode發(fā)起,第一次是全量匯報,后面只匯報增量。全量處理時,會在上報的鏈表頭放一個虛Block作為分隔符,每處理一個就放到分隔符前面。全部處理完后,在分隔符后的,就是需要刪除的。
3.Datanode管理
數(shù)據(jù)塊和數(shù)據(jù)節(jié)點(diǎn)的映射,即triplet,使用Block→DataNodeStorageInfo的映射
Decommission的時候需要確保該Datanode上所有數(shù)據(jù)的副本數(shù)都達(dá)標(biāo)才能標(biāo)記為已Decommission
Datanode注冊的時候,需要區(qū)分從未注冊過,使用不同StorageId重復(fù)注冊,使用相同StorageId注冊的三種情況。
收到Datanode心跳時,會根據(jù)其存儲信息更新全局信息,檢查其損壞Block和Storage,并且下發(fā)已經(jīng)準(zhǔn)備的命令。
4.租約管理
租約按照文件賦予客戶端,關(guān)鍵是LeaseManagerd的leases,sortedLeases和sortedLeasesByPath三個對象
只有UnderConstruction的數(shù)據(jù)塊才會有l(wèi)ease,已經(jīng)構(gòu)建完畢的都是只讀的。
租約恢復(fù)的過程,選取一個最近一次進(jìn)行匯報的數(shù)據(jù)節(jié)點(diǎn)作為主恢復(fù)節(jié)點(diǎn),然后向這個數(shù)據(jù)節(jié)點(diǎn)發(fā)送租約恢復(fù)指令,主恢復(fù)數(shù)據(jù)節(jié)點(diǎn)接收到指令后,會調(diào)用Datanode.recoverBlock()方法開始租約恢復(fù),這個方法首先會向數(shù)據(jù)流管道中參與租約恢復(fù)的數(shù)據(jù)節(jié)點(diǎn)收集副本信息,然后從該數(shù)據(jù)塊的所有副本中選取一個最好的狀態(tài)作為所有副本恢復(fù)的目標(biāo)狀態(tài)(多個副本中選擇最小長度作為最終更新一致的標(biāo)準(zhǔn))。確后主恢復(fù)節(jié)點(diǎn)會同步所有Datanode上該數(shù)據(jù)塊副本至目標(biāo)狀態(tài)。同步結(jié)束后,這些數(shù)據(jù)節(jié)點(diǎn)上的副本長度和時間戳將一致。最后,主恢復(fù)節(jié)點(diǎn)會向NameNode報告這次租約恢復(fù)的結(jié)果。NameNode 更新文件 block 元數(shù)據(jù)信息,收回該文件租約,并關(guān)閉文件。
Datanode
1.Datanode架構(gòu)
hdfs2.6引入了Federation架構(gòu),為了支持拓展,Namenode可以有多個,每個管理不同的Namespace。在BlockStorage這一層引入了BlockPool。每個BlockPool對應(yīng)一個Namenode管理的Namespace。

BlockPoolManager管理BlockPool并先Namenode匯報。DataBlockScanner檢查數(shù)據(jù)塊校驗(yàn)和,DirectoryScanner掃描目錄與其下屬的文件元數(shù)據(jù)是否對應(yīng),并且更新。DataStorage負(fù)責(zé)管理與組織 Datanode 的磁盤存儲空間,管理存儲空間的生命周期,包括升級、回滾、提交等操作。通過BlockPoolSliceStorage管理BlockPool。FsDataset管理數(shù)據(jù)塊的所有操作,例如創(chuàng)建數(shù)據(jù)塊文件、維護(hù)數(shù)據(jù)塊文件和校驗(yàn)和文件的對應(yīng)關(guān)系等。用FSVolumeImpl來管理每個存儲目錄。

2.Datanode Storage
升級時需要考慮回滾,hdfs的方案是復(fù)制一份原有數(shù)據(jù)進(jìn)行備份。但是,這樣需要保留額外的1/2空間,hdfs使用linux硬鏈接的方式,將新舊版本中相同的部分指向同一個文件。
文件組織架構(gòu)如下,注意,pool在current的目錄下,這樣可以支持單個pool進(jìn)行升級。每個BlockPool的數(shù)據(jù)可以分布在不同的文件目錄下。


所有文件目錄相關(guān)的操作都可以在StorageDirectory中進(jìn)行,它是Storage的內(nèi)部類。
Datanode啟動時,會掃描配置文件中指定的目錄,找到每個blockpool目錄。然后分析并恢復(fù)每個目錄的狀態(tài),最后調(diào)用doTransition()進(jìn)行啟動。
DataStorage負(fù)責(zé)維護(hù)上述目錄的組織架構(gòu),在升級或者回滾時進(jìn)行文件和文件夾的鏈接和重命名等操作。
3.FsDatasetImpl
數(shù)據(jù)塊有RBW(可以寫),F(xiàn)INALIZED(構(gòu)建完成),RUR(恢復(fù)中),RWR(等待恢復(fù)),TEMPORARY(Datanode之間再在復(fù)制的副本)等狀態(tài)。
啟動Datanode時,需要讀取所有FINALIZED和RBW數(shù)據(jù)塊的元信息到內(nèi)存。
升級后的數(shù)據(jù)塊使用硬鏈接減少空間使用,但如果這時對該數(shù)據(jù)塊進(jìn)行apppend,那么會影響原有數(shù)據(jù)。此時需要copy-on-write,將現(xiàn)有數(shù)據(jù)復(fù)制一份再進(jìn)行append。
FsVolumeList管理一個Datanode下的所有存儲目錄,每個存儲目錄對應(yīng)的對象是FsVolumeImpl,F(xiàn)sVolumeImpl管理多個BlockPool,BlockPool對應(yīng)的對象是BlockPoolSlice。FsDatasetImpl會管理所有的數(shù)據(jù)塊。
添加副本時有輪詢和選擇最多剩余空間兩個策略。
FsDatasetImpl維護(hù)一個ReplicaMap,是BlockPoolID→Map<Block,ReplicaInfo>的映射,拿到對應(yīng)BlockPool的Map后就能根據(jù)Block對象得到副本信息。在添加RBW和FINALIZED的數(shù)據(jù)塊時會更新ReplicaMap,后臺也會定期掃描數(shù)據(jù),更新ReplicaMap。
數(shù)據(jù)塊將通過以下的函數(shù)在狀態(tài)間進(jìn)行跳轉(zhuǎn)。
執(zhí)行Append前需要invalid cache并unlink硬鏈接。FINALIZED的Block也能append,但是需要把狀態(tài)改成RBW。
FINALIZED前需要檢查是否需要Recovery,需要的話先進(jìn)行Recovery,不需要就直接移動下目錄,更新ReplicaMap即可。
啟動時也需要對數(shù)據(jù)塊進(jìn)行恢復(fù),調(diào)用的是initReplicaRecovery → updateReplicaUnderRecovery,主要做各種檢查,并且根據(jù)所有副本的最小長度truncate數(shù)據(jù)。。
HDFS的Cache是指令式的cahce,由用戶手動指明需要緩存哪些數(shù)據(jù)。由FsDatasetCache維護(hù)
FsDatasetImpl負(fù)責(zé)向Namenode匯報數(shù)據(jù)塊信息。
4.BlockPoolManager
BlockPoolManager是BlockPool的對外接口,負(fù)責(zé)向Namenode匯報數(shù)據(jù)并執(zhí)行帶回的命令。

BPServiceActor負(fù)責(zé)和Namenode匯報數(shù)據(jù),心跳等信息。在啟動時,會連接Namenode,判斷其是否是自己第一個連接的Namenode,如果是,就向其注冊自己。心跳回包中還有Namenode切換的信息,需要根據(jù)Txn id以及其聲明判斷是否需要進(jìn)行更換匯報的Namenode。
blockReport負(fù)責(zé)全量數(shù)據(jù)塊匯報,IncrementalBlockReportManager維護(hù)了一個Map,記錄增量和刪除的數(shù)據(jù)塊,在IncrementalBlockReportManager.sendIBRs中進(jìn)行增量匯報。
BPOfferService更多的是一個對外的接口,包裝了一下兩個BPServiceActor,然后處理一下BPServiceActor傳來的關(guān)于Namenode的指令。
BlockPoolManager也很簡單,就是管理一下BPOfferService,處理Namenode的增加,刪除和refresh操作,對應(yīng)增加/刪除/更新BPOfferService。
5.流式接口
讀寫數(shù)據(jù)的接口在DataTransferProtocol中,請求的pb格式
Datanode通過DataXceiverServer監(jiān)聽所有請求,并創(chuàng)建DataXceiver響應(yīng)對應(yīng)請求。Hadoop很喜歡使用建立連接和處理邏輯相隔離的設(shè)計(jì),和Bytedrive一樣,是為了提高前臺的處理能力。
DataXceiver會先解析傳入的數(shù)據(jù)流,然后創(chuàng)建對應(yīng)的對象進(jìn)行處理,readop會創(chuàng)建BlockSender,以下列格式發(fā)送數(shù)據(jù)。
讀數(shù)據(jù)

BlcokSender發(fā)送數(shù)據(jù)一共分為三步,首先根據(jù)傳入的數(shù)據(jù)計(jì)算校驗(yàn)和,檢驗(yàn)時間戳,offset等各種東西,第二步進(jìn)預(yù)讀取,把數(shù)據(jù)從磁盤讀到內(nèi)存,第三步讀取第二步中讀上來的數(shù)據(jù),調(diào)用sendPacket把數(shù)據(jù)構(gòu)造成上述格式,寫到輸出流中。
可以將transferTo設(shè)置為true來使用操作系統(tǒng)的零拷貝特性,減少兩次內(nèi)存拷貝和內(nèi)核態(tài)切換。但是網(wǎng)卡層面不支持計(jì)算校驗(yàn)和,所以校驗(yàn)需要在用戶層進(jìn)行。
限流在DataTransferThrottler進(jìn)行,原理很簡單,通過計(jì)算當(dāng)前總流量和流量上限,得到剩余流量,如果現(xiàn)在這個請求的流量大于剩余流量,就wait一段時間,如果沒有,減掉這個請求流量,直接返回。
寫數(shù)據(jù)
HDFS寫入請求遵循下面的邏輯,使用Chain Replica來復(fù)制數(shù)據(jù),寫入成功后會級聯(lián)返回。
通過DataXceiver.writeBlock()來執(zhí)行寫入邏輯,構(gòu)建上下游的輸入輸出流,用BlockReceiver轉(zhuǎn)發(fā)數(shù)據(jù)。BlockReceiver中關(guān)鍵的方法是receivePacket,會接收上游數(shù)據(jù)并轉(zhuǎn)發(fā)給下游,如果是最后一個節(jié)點(diǎn),會進(jìn)行落盤并返回響應(yīng)。
響應(yīng)在PacketResponder中進(jìn)行處理,使用生產(chǎn)者-消費(fèi)者模型,會復(fù)制下游響應(yīng)給上游。如果是OOB響應(yīng),則直接轉(zhuǎn)發(fā)。如果有錯誤,轉(zhuǎn)發(fā)給上游,并停止此次的發(fā)送和接收線程。如果是Packet中的最后一個chunk的響應(yīng),則調(diào)用finalizeBlock上報給Namenode。

總結(jié)
HDFS是非常經(jīng)典的分布式文件系統(tǒng),由于經(jīng)過多年的廣泛使用,已經(jīng)相當(dāng)穩(wěn)定。由于其在Hadoop生態(tài)的獨(dú)特地位,許多公司在起步階段都會把HDFS作為離線存儲的首選方案。
雖然HDFS的一些設(shè)計(jì)在如今看來有些過時,比如Namenode的主備方案可以使用Raft。但是在工程實(shí)踐上的許多細(xì)節(jié),比如Datanode鏈?zhǔn)綄懭霑r的級聯(lián)報錯,不持久化Block到Datanode的信息等,仍然有許多學(xué)習(xí)的價值。
編輯于 2023-04-09 17:03?IP 屬地上海
HDFS
軟件介紹
產(chǎn)品介紹