HDFS追本溯源:租約,讀寫過程的容錯處理及NN的主要數據結構
1. Lease 的機製:
hdfs支持write-once-read-many,也就是說不支持並行寫,那麼對讀寫的互斥同步就是靠Lease實現的。Lease說白了就是一個有時間約束的鎖。客戶端寫文件時需要先申請一個Lease,對應到namenode中的LeaseManager,客戶端的client name就作為一個lease的holder,即租約持有者。LeaseManager維護了文件的path與lease的對應關係,還有clientname->lease的對應關係。LeaseManager中有兩個時間限製:softLimitand hardLimit。
軟限製就是寫文件時規定的租約超時時間,硬限製則是考慮到文件close時未來得及釋放lease的情況強製回收租約。
LeaseManager中還有一個Monitor線程來檢測Lease是否超過hardLimit。而軟租約的超時檢測則在DFSClient的LeaseChecker中進行。
當客戶端(DFSClient)create一個文件的時候,會通過RPC 調用 namenode 的createFile方法來創建文件。進而又調用FSNameSystem的startFile方法,又調用 LeaseManager 的addLease方法為新創建的文件添加一個lease。如果lease已存在,則更新該lease的lastUpdate (最近更新時間)值,並將該文件的path對應該lease上。之後DFSClient 將該文件的path 添加 LeaseChecker中。文件創建成功後,守護線程LeaseChecker會每隔一定時間間隔renew該DFSClient所擁有的lease。
LeaseManagement是HDFS中的一個同步機製,用於保證同一時刻隻有一個client對一個文件進行寫或創建操作。如當 新建一個文件f時,client向NameNode發起一個create請求,那麼leaseManager會想該client分配一個f文件的 lease。client憑借該lease完成文件的創建操作。此時其他client無法獲得f的當client長時間(默認為超過1min)不進行操作 時,發放的lease將被收回。
LeaseManager主要完成兩部分工作:
- 文件create,write,complete操作時,創建lease、更新時間戳、回收lease
- 一個後台線程定期檢查是否有過期的lease
LeaseManager的代碼結構如下
其中Lease表示一個租約,包括一個client(holder)所擁有的所有文件鎖(paths)。
Monitor是檢查是否有過期租約的線程。
LeaseManager中有幾個主要數據結構:
- leases(TreeMap<String, Lease>):維護holder -> leased的映射集合
- sortedLeases (TreeSet): lease集合
- sortedLeaseByPath(TreeMap<String, Lease>): 維護paths->lease的映射集合
一、創建lease
當client向NameNode發起create操作時,NameNode.create()調用FSNameSystem.startFile()->FSNameSystem.startFileInternal(),該方法最終會調用 leaseManager.addLease(cons.clientName, src)來創建lease。
LeaseRecovery ——租約回收
leaserecovery時機
lease發放之後,在不用時會被回收,回收的產經除上述Monitor線程檢測lease過期是回收外,還有:
- NameNode收到DataNode的Sync block command時
- DFSClient主動關閉一個流時
- 創建文件時,如果該DFSClient的lease超過soft limit時
lease recovery 算法
1) NameNode查找lease信息
2) 對於lease中的每個文件f,令b為f的最後一個block,作如下操作:
2.1) 獲取b所在的datanode列表
2.2) 令其中一個datanode作為primarydatanode p
2.3) p 從NameNode獲取最新的時間戳
2.4) p 從每個DataNode獲取block信息
2.5) p 計算最小的block長度
2.6) p 用最小的block長度和最新的時間戳來更新具有有效時間戳的datanode
2.7) p 通知NameNode更新結果
2.8) NameNode更新BlockInfo
2.9) NameNode從lease中刪除f,如果此時該lease中所有文件都已被刪除,將刪除該lease
2.10) Name提交修改的EditLog
Client續約 —— DFSClient.LeaseChecker
在NameNode上的LeaseManager.Monitor線程負責檢查過期的lease,那麼client為了防止尚在使用的lease過期,需要定期想NameNode發起續約請求。該任務有DFSClient中的LeaseChecker完成。
LeaseChecker結構如下:
其中pendingCreates是一個TreeMap<String, OutputStream>用來維護src->當前正在寫入的文件的DFSOutputStream的映射。
其核心是周期性(每個1s)調用run()方法來對租約過半的lease進行續約
NameNode接收到renewLease請求後,調用FSNameSystem.renewLease()並最終調用LeaseManager.renewLease()完成續約。
2. 機架感知
HDFS機架感知
通常,大型 Hadoop 集群是以機架的形式來組織的,同一個機架上不同 節點間的網絡狀況比不同機架之間的更為理想。 另外, NameNode 設法將 數據塊副本保存在不同的機架上以提高容錯性。
而 HDFS 不能夠自動判斷集群中各個 datanode 的網絡拓撲情況 Hadoop允 許集群的管理員通過配置 dfs.network.script 參數來確定節點所處的機架。 文件提供了 IP->rackid 的翻譯。 NameNode 通過這個得到集群中各個 datanode 機器的 rackid 。 如果 topology.script.file.name 沒有設定,則每個 IP 都會翻譯 成/ default-rack 。
有了機架感知, NameNode 就可以畫出上圖所示的 datanode 網絡拓撲圖。D1,R1 都是交換機,最底層是 datanode 。 則 H1 的 rackid=/D1/R1/H1 , H1的 parent 是 R1 , R1 的是 D1 。 這些 rackid 信息可以通過topology.script.file.name 配置。有了這些 rackid 信息就可以計算出任意兩台datanode 之間的距離。
distance(/D1/R1/H1,/D1/R1/H1)=0 相同的 datanode
distance(/D1/R1/H1,/D1/R1/H2)=2 同一 rack 下的不同 datanode
distance(/D1/R1/H1,/D1/R1/H4)=4 同一 IDC 下的不同 datanode
distance(/D1/R1/H1,/D2/R3/H7)=6 不同 IDC 下的 datanode
3. HDFS 文件刪除恢複機製
當用戶或應用程序刪除某個文件時,這個文件並沒有立刻從 HDFS 中刪除。實際上, HDFS 會將這個文件重命名轉移到 /trash 目錄。隻要文件還在/trash 目錄中,該文件就可以被迅速地恢複。文件在 /trash 中保存的時間是可 配置的,當超過這個時間時, Namenode 就會將該文件從名字空間中刪除。 刪除文件會使得該文件相關的數據塊被釋放。注意,從用戶刪除文件到 HDFS 空閑空間的增加之間會有一定時間的延遲。
隻要被刪除的文件還在 /trash 目錄中,用戶就可以恢複這個文件。如果 用戶想恢複被刪除的文件,他 / 她可以瀏覽 /trash 目錄找回該文件。 /trash 目 錄僅僅保存被刪除文件的最後副本。 /trash 目錄與其他的目錄沒有什麼區別 ,除了一點:在該目錄上 HDFS 會應用一個特殊策略來自動刪除文件。目前 的默認策略是刪除 /trash 中保留時間超過 6 小時的文件。將來,這個策略可以 通過一個被良好定義的接口配置。
開啟回收站
Hdfs -site.xml
<configuration>
<property>
<name>fs.trash.interval</name>
<value> 1440 </value>
<description>Number ofminutes betweentrash checkpoints.
If zero,the trashfeature is disabled.
</description>
</property>
</configuration>
1, fs.trash.interval 參數設置保留時間為 1440 秒 (1 天 )
2, 回收站的位置:在 HDFS 上的 / user/$USER/.Trash/Current/
4. 數據完整性
從某個 Datanode 獲取的數據塊有可能是損壞的,損壞可能是由Datanode 的存儲設備錯誤、網絡錯誤或者軟件 bug 造成的。 HDFS 客戶端軟 件實現了對 HDFS 文件內容的校驗和 (checksum) 檢查。當客戶端創建一個新 的HDFS 文件,會計算這個文件每個數據塊的校驗和,並將校驗和作為一個 單獨的隱藏文件保存在同一個 HDFS 名字空間下。當客戶端獲取文件內容後 ,它會檢驗從Datanode 獲取的數據跟相應的校驗和文件中的校驗和是否匹 配,如果不匹配,客戶端可以選擇從其他 Datanode 獲取該數據塊的副本。
5. 修改副本數
1.集群隻有三個Datanode,hadoop係統replication=4時,會出現什麼情況?
對於上傳文件到hdfs上時,當時hadoop的副本係數是幾,這個文件的塊數副本數就會有幾份,無論以後你怎麼更改係統副本係統,這個文件的副本數都不 會改變,也就說上傳到分布式係統上的文件副本數由當時的係統副本數決定,不會受replication的更改而變化,除非用命令來更改文件的副本數。因為 dfs.replication實質上是client參數,在create文件時可以指定具體replication,屬性dfs.replication是不指定具體replication時的采用默認備份數。文件上傳後,備份數已定,修改dfs.replication是 不會影響以前的文件的,也不會影響後麵指定備份數的文件。隻影響後麵采用默認備份數的文件。但可以利用hadoop提供的命令後期改某文件的備份 數:hadoop fs-setrep -R 1。如果你是在hdfs-site.xml設置了dfs.replication,這並一定就得了,因為你可能沒把conf文件夾加入到你的project的classpath裏,你的程序運行時取的dfs.replication可能是hdfs-default.xml裏的dfs.replication,默認是3。可能這個就是造成你為什麼dfs.replication老是3的原因。你可以試試在創建文件時,顯式設定 replication。replication一般到3就可以了,大了意義也不大。
6. HDFS的安全模式
Namenode 啟動後會進入一個稱為安全模式的特殊狀態。處於安全模式 的Namenode 是不會進行數據塊的複製的。 Namenode 從所有的 Datanode 接收心跳信號和塊狀態報告。塊狀態報告包括了某個 Datanode 所有的數據 塊列表。每個數據塊都有一個指定的最小副本數。當 Namenode 檢測確認某 個數據塊的副本數目達到這個最小值,那麼該數據塊就會被認為是副本安全 (safely replicated) 的;在一定百分比(這個參數可配置)的數據塊被 Namenode 檢測確認是安全之後(加上一個額外的 30 秒等待時間), Namenode 將退出安全模式狀態。接下來它會確定還有哪些數據塊的副本沒 有達到指定數目,並將這些數據塊複製到其他 Datanode上。
7. 讀過程分析
•使用HDFS提供的客戶端開發庫Client,向遠程的Namenode發起RPC請求;
• Namenode會視情況返回文件的部分或者全部block列表,對於每個block,Namenode都會返回有該block拷貝的DataNode地址;
•客戶端開發庫Client會選取離客戶端最接近的DataNode來讀取block;如果客戶端本身就是DataNode,那麼將從本地直接獲取數據.
•讀取完當前block的數據後,關閉與當前的DataNode連接,並為讀取下一個block尋找最佳的DataNode;
•當讀完列表的block後,且文件讀取還沒有結束,客戶端開發庫會繼續向Namenode獲取下一批的block列表。
•讀取完一個block都會進行checksum驗證,如果讀取datanode時出現錯誤,客戶端會通知Namenode,然後再從下一個擁有該block拷貝的datanode繼續讀。
8. 寫過程流程分析
•使用HDFS提供的客戶端開發庫Client,向遠程的Namenode發起RPC請求;
•Namenode會檢查要創建的文件是否已經存在,創建者是否有權限進行操作,成功則會為文件 創建一個記錄,否則會讓客戶端拋出異常;
•當客戶端開始寫入文件的時候,會將文件切分成多個packets,並在內部以數據隊列"data queue"的形式管理這些packets,並向Namenode申請新的blocks,獲取用來存儲replicas的合適的datanodes列表, 列表的大小根據在Namenode中對replication的設置而定。
•開始以pipeline(管道)的形式將packet寫入所有的replicas中。把packet以流的方式寫入第一個datanode, 該datanode把該packet存儲之後,再將其傳遞給在此pipeline中的下一個datanode,直到最後一個datanode,這種寫數據 的方式呈流水線的形式。
•最後一個datanode成功存儲之後會返回一個ack packet,在pipeline裏傳遞至客戶端,在客戶端的開發庫內部維護著"ack queue",成功收到datanode返回的ackpacket後會從"ackqueue"移除相應的packet。
•如果傳輸過程中,有某個datanode出現了故障,那麼當前的pipeline會被關閉,出現故障的datanode會從當前的pipeline中移除,剩餘的block會繼續剩下的datanode中繼續以pipeline的形式傳輸,同時Namenode會分配一個新的datanode,保持replicas設定的數量。
流水線複製
當客戶端向 HDFS 文件寫入數據的時候,一開始是寫到本地臨時文件中。假設該文件的副 本係數設置為 3 ,當本地臨時文件累積到一個數據塊的大小時,客戶端會從 Namenode 獲取一個 Datanode 列表用於存放副本。然後客戶端開始向第一個 Datanode 傳輸數據,第一個 Datanode 一小部分一小部分 (4 KB) 地接收數據,將每一部分寫入本地倉庫,並同時傳輸該部分到列表中 第二個 Datanode節點。第二個 Datanode 也是這樣,一小部分一小部分地接收數據,寫入本地 倉庫,並同時傳給第三個 Datanode 。最後,第三個 Datanode 接收數據並存儲在本地。因此, Datanode 能流水線式地從前一個節點接收數據,並在同時轉發給下一個節點,數據以流水線的 方式從前一個 Datanode 複製到下一個
更細節的原理
客戶端創建文件的請求其實並沒有立即發送給 Namenode ,事實上,在剛開始階 段 HDFS 客戶端會先將文件數據緩存到本地的一個臨時文件。應用程序的寫操作被透 明地重定向到這個臨時文件。當這個臨時文件累積的數據量超過一個數據塊的大小 ,客戶端才會聯係 Namenode 。 Namenode 將文件名插入文件係統的層次結構中,並 且分配一個數據塊給它。然後返回 Datanode 的標識符和目標數據塊給客戶端。接著 客戶端將這塊數據從本地臨時文件上傳到指定的 Datanode 上。當文件關閉時,在臨 時文件中剩餘的沒有上傳的數據也會傳輸到指定的 Datanode 上。然後客戶端告訴 Namenode 文件已經關閉。此時 Namenode 才將文件創建操作提交到日誌裏進行存儲 。如果 Namenode 在文件關閉前宕機了,則該文件將丟失。
整個寫流程如下:
第一步,客戶端調用DistributedFileSystem的create()方法,開始創建新文件:DistributedFileSystem創建DFSOutputStream,產生一個RPC調用,讓NameNode在文件係統的命名空間中創建這一新文件;
第二步,NameNode接收到用戶的寫文件的RPC請 求後,誰偶先要執行各種檢查,如客戶是否有相關的創佳權限和該文件是否已存在等,檢查都通過後才會創建一個新文件,並將操作記錄到編輯日誌,然後DistributedFileSystem會將DFSOutputStream對象包裝在FSDataOutStream實例中,返回客戶端;否則文件 創建失敗並且給客戶端拋IOException。
第三步,客戶端開始寫文 件:DFSOutputStream會將文件分割成packets數據包,然後將這些packets寫到其內部的一個叫做dataqueue(數據隊列)。dataqueue會向NameNode節點請求適合存儲數據副本的DataNode節點的列表,然後這些DataNode之前生成一個Pipeline數據流管 道,我們假設副本集參數被設置為3,那麼這個數據流管道中就有三個DataNode節點。
第四步,首先DFSOutputStream會將packets向Pipeline數據流管道中的第一個DataNode節點寫數據,第一個DataNode接收packets然後把packets寫向Pipeline中的第二個節點,同理,第二個節點保存接收到的數據然後將數據寫向Pipeline中的第三個DataNode節點。
第五步,DFSOutputStream內部同樣維護另 外一個內部的寫數據確認隊列——ackqueue。當Pipeline中的第三個DataNode節點將packets成功保存後,該節點回向第二個DataNode返回一個確認數據寫成功的 信息,第二個DataNode接收到該確認信息後在當前節點數據寫成功後也會向Pipeline中第一個DataNode節點發送一個確認數據寫成功的信 息,然後第一個節點在收到該信息後如果該節點的數據也寫成功後,會將packets從ackqueue中將數據刪除。
在寫數據的過程中,如果Pipeline數據流管道中的一個DataNode節點寫失敗了會發生什問題、需要做哪些內部處理呢?如果這種情況發生,那麼就會執行一些操作:
首先,Pipeline數據流管道會被關閉,ack queue中的packets會被添加到dataqueue的前麵以確保不會發生packets數據包的丟失;
接著,在正常的DataNode節點上的以保存好的block的ID版本會升級——這樣發生故障的DataNode節點上的block數據會在節點恢複正常後被刪除,失效節點也會被從Pipeline中刪除;
最後,剩下的數據會被寫入到Pipeline數據流管道中的其他兩個節點中。
如果Pipeline中的多個節點在寫數據是發生失敗,那麼隻要寫成功的block的數量達到dfs.replication.min(默認為1),那麼就任務是寫成功的,然後NameNode後通過一步的方式將block複製到其他節點,最後事數據副本達到dfs.replication參數配置的個數。
第六步,,完成寫操作後,客戶端調用close()關閉寫操作,刷新數據;
第七步,,在數據刷新完後NameNode後關閉寫操作流。到此,整個寫操作完成。
least recently used
9. HDFS負載均衡
HDFS的數據也許並不是非常均勻的分布在各個DataNode中。一個常見的原因是在現有的集群上經常會增添新的DataNode節點。當新增一個 數據塊(一個文件的數據被保存在一係列的塊中)時,NameNode在選擇DataNode接收這個數據塊之前,會考慮到很多因素。其中的一些考慮的是:
•將數據塊的一個副本放在正在寫這個數據塊的節點上。
•盡量將數據塊的不同副本分布在不同的機架上,這樣集群可在完全失去某一機架的情況下還能存活。
•一個副本通常被放置在和寫文件的節點同一機架的某個節點上,這樣可以減少跨越機架的網絡I/O。
•盡量均勻地將HDFS數據分布在集群的DataNode中。
10. 基本數據結構
FSNameSystem
FSNameSystem是HDFS文件係統實際執行的核心,提供各種增刪改查文件操作接口。其內部維護多個數據結構之間的關係:
- fsname->block列表的映射
- 所有有效blocks集合
- block與其所屬的datanodes之間的映射(該映射是通過block reports動態構建的,維護在namenode的內存中。每個datanode在啟動時向namenode報告其自身node上的block)
- 每個datanode與其上的blocklist的映射
- 采用心跳檢測根據LRU算法更新的機器(datanode)列表
FSDirectory
FSDirectory用於維護當前係統中的文件樹。
其內部主要組成結構包括一個INodeDirectoryWithQuota作為根目錄(rootDir)和一個FSImage來持久化文件樹的修改操作。
INode
HDFS中文件樹用類似VFS中INode的方式構建,整個HDFS中文件被表示為INodeFile,目錄被表示為INodeDirectory。INodeDiretoryWithQuota是INodeDirectory的擴展類,即帶配額的文件目錄
INodeFile表示INode書中的一個文件,擴展自INode,除了名字(name),父節點(parent)等之外,一個主要元素是blocks,一個BlockInfo數組,表示該文件對應的block信息。
BlocksMap
BlocksMap用於維護Block-> { INode, datanodes, self ref } 的映射 BlocksMap結構比較簡單,實際上就是一個Block到BlockInfo的映射。
Block
Block是HDFS中的基本讀寫單元,主要包括:
- blockId: 一個long類型的塊id
- numBytes: 塊大小
- generationStamp: 塊更新的時間戳
BlockInfo
BlockInfo擴展自Block,除基本信息外還包括一個inode引用,表示該block所屬的文件;以及一個神奇的三元組數組Object[] triplets,用來表示保存該block的datanode信息,假設係統中的備份數量為3。那麼這個數組結構如下:
- DN1,DN2,DN3分別表示存有改block的三個datanode的引用(DataNodeDescriptor)
- DN1-prev-blk表示在DN1上block列表中當前block的前置block引用
- DN1-next-blk表示在DN1上block列表中當前block的後置block引用
DN2,DN3的prev-blk和next-blk類似。 HDFS采用這種結構存放block->datanodelist的信息主要是為了節省內存空間,block->datanodelist之間的映射關係需要占用大量內存,如果同樣還要將datanode->blockslist的信息保存在內存中,同樣要占用大量內存。采用三元組這種方式能夠從其中一個block獲得到改 block所屬的datanode上的所有block列表。
FSImage
FSImage用於持久化文件樹的變更以及係統啟動時加載持久化數據。HDFS啟動時通過FSImage來加載磁盤中原有的文件樹,係統Standby之後,通過FSEditlog來保存在文件樹上的修改,FSEditLog定期將保存的修改信息刷到FSImage中進行持久化存儲。FSImage中文件元信息的存儲結構如下(參見FImage.saveFSImage()方法)
FSImage頭部信息
- layoutVersion(int):image layout版本號,0.19版本的hdfs中為-18
- namespaceId(int): 命名空間ID,係統初始化時生成,在一個namenode生命周期內保持不變,datanode想namenode注冊是返回改id作為 registerId,以後每次datanode與namenode通信時都攜帶該id,不認識的id的請求將被拒絕。
- numberItemOfTree(long): 係統中的文件總數
- generationTimeStamp: 生成image的時間戳
參考資料:
1. https://blog.csdn.net/cklsoft/article/details/8917899
2. https://www.iteye.com/topic/1126509
3. https://jiangbo.me/blog/2012/10/18/hdfs-namenode-lease-management/
4. https://flyingdutchman.iteye.com/blog/1900536
最後更新:2017-04-03 12:56:27