Hadoop的官方文檔中,對於HDFS的升級建議分三個步驟,1,先停掉HDFS服務,再啟動,HDFS合並FsEditLog到FsImage 之中,再停掉HDFS服務,2,備份namenode的meta文件,在新版本HDFS安裝目錄的配置文件中,配置namenode的meta文件目錄指 向舊有的meta文件目錄,以-upgrade選項啟動HDFS,讓HDFS服務執行升級過程,3,升級進度達到100%後,執行hadoop dfsadmin -finlizeUpgrade告訴HDFS服務,升級結束。如果升級失敗,以-rollback選項啟動HDFS,執行回滾。
從升級到本身使命來說,升級到目的就是為了保護現有數據,如果在升級過程中,出現因為升級而導致數據丟失,或者整個HDFS文件系統的損壞,那升級 就失敗了。看完Hadoop官方文檔的升級過程和命令後,我很好奇,如此簡單的描述,能保證升級過程一定是“達到”目的的嗎?或者至少保證不會損壞 HDFS文件系統本身!
懷疑是懷疑,等了解它,再對它下結論!然而HDFS升級的資料非常少,這裡從源碼分析,力求做到最接近作者升級設計的意圖。
在HDFS文件系統中,NameNode的一個基本職責是分配塊到datanode上;告訴客戶端一個塊的內容可以從哪台機器上能讀取到。塊信息對 於HDFS文件系統來說,是非常重要的,為了不丟失塊信息,並能讓客戶端,datanode能快速懂檢索塊信息,HDFS的開發者們在設計 NameNode的存儲結構時,借鑒了數據庫的WAH一些做法,但同時又采用了一種非常不常見到做法。
為了保證塊信息不丟失,HDFS在分配一個塊時,先寫塊信息的日志到磁盤的文件系統,這個存儲在HDFS中叫fsEditLog,日志寫成功後,再 向處於JVM heap中的一種可以存儲重復HASH值條目的Map中,添加這個塊信息,如果是刪除塊操作,則是從這個Map中刪除相應的條目。這個Map中保存了 HDFS中所有塊信息,而不是常見的緩存部分數據。在HDFS的namenode存儲中,磁盤上存儲的塊信息和Map中存儲的塊信息的實際數量是一樣的。 為了保證這個Map所存儲的數據不超過JVM Heap的限制,Namenode在啟動的時候,默認JVM的-Xx啟動參數值的2%作為計算這個Map最多可以容納條目的數量,Map中的條目達到這個 最大值時,則不可以添加新的塊。在磁盤端,塊信息主要存儲在fsImage中,塊操作日志存儲在fsEditLog中,自fsImage文件中最後的事務 點後,對於塊的操作是首先順序對追加到fsEditLog中,而不是直接寫入fsImage。HDFS服務在啟動時會啟動一個checkpointer組 件,定期的把當前的fsImage和fsEditLog合並到新的fsImage中。
NameNode的meta內部物理結構如下圖所示:`-- current |-- edits_0000000000000000001-0000000000000000007 |-- edits_inprogress_0000000000000000008 |-- fsimage_0000000000000000000 |-- fsimage_0000000000000000000.md5 |-- seen_txid `-- VERSION
從存儲上看HDFS的存儲結構,只有在HDFS啟動時,從fsImage中載人塊信息到內存端的Map,或者在需要時,把內存端的Map寫入磁盤端 去。對於塊的增、刪、改這樣的操作,內存端和磁盤端獨立,日志能保證最終的塊信息在兩端是一致的。這樣設計的好處是,簡單,容易實現,缺點是內存端大小會 受到限制,不容易擴展。
相對NameNode對於塊的存儲結構,DataNode上對於塊的存儲結構簡單很多,DataNode只存儲<塊,大小>對。一個塊 被寫入到DataNode上後,會寫一個和這個塊名相同的meta文件,存儲塊大小和塊生成的時間。DataNode中,對塊的生命周期進行了細致的劃 分,當向文件追加的新塊時,塊的是位於RBW目錄(1.x則是在BlockBeingWritten目錄),當這個塊達到系統指定的塊大小或者文件寫關 閉,這個塊會變成finallized狀態,DataNode把此塊移動到current/finallized目錄中。
在HDFS 2.x中,對於數據塊的存儲是按照BlockPoolSlice來管理的,從邏輯上說,一個BlockPoolSlice代表DataNode配置項 dfs.datanode.data.dir中的一個目錄。DataNode上新建塊,移動和刪除塊是通過BlockPoolSlice完成邏輯上的操 作。BlockPoolSlice內部的物理結構如下圖所示:
|-- current | |-- BP-1134876178-10.1.101.51-1427874067591 | | |-- current | | | |-- dfsUsed | | | |-- finalized | | | |-- rbw | | | `-- VERSION | | |-- dncp_block_verification.log.curr | | |-- dncp_block_verification.log.prev | | `-- tmp | `-- VERSION |-- detach |-- in_use.lock |-- storage `-- tmp
對於NameNode meta文件的升級,NameNode在啟動時,如果分析帶有upgrade命令選項,會升級meta文件夾中的current目錄,現在假設HDFS剛 重啟過一次,並沒有新的塊添加到HDFS中,此時,fsEditLog都是空的,對於此目錄的操作按照下面的操作步驟進行。
如果meta文件夾中有previous目錄,刪除它
把current重名為previous.tmp
新建current目錄
把previous.tmp文件下的VERSION文件升級為當前安裝的版本,寫入current目錄
載人previous.tmp文件下fsImage到中的文件和塊到內存,載入這個過程是向下兼容的,經載入後的文件和塊已經被轉化成當前安裝版本的格式。
舊版本fsImage中的genStamp、clusterId、numFiles信息保留不變,但舊版本的fsImage文件名和fsEditLog文件可能會改變
在fsImage載入成功後,把處於內存端的文件和塊按照最新的格式寫入磁盤的current目錄,並且在這個時候,告訴系統,NameNode的meta文件升級操作完成!
從這個過程中,可以看出,NameNode在升級的過程中,並沒有在舊版本的fsImage和fsEditLog上直接進行操作,而是新建一個文件夾,這樣的方式是非常安全的。首先解開了開篇的一半疑問。
DataNode升級的過程和NameNode的fsImage升級的方式比較類似,但在具體操作上有很大的不同,DataNode上存儲的主要是 數據塊,如果采用復制數據塊到新的目錄,那復制PB,EB級別的數據,這個過程將變得不可想象。大師們為DataNode的升級尋找到了一個合適的方案。
對於一個BlockPoolSlice的物理文件夾,升級執行下面的步驟
如果BlockPoolSlice的物理文件夾中有previous,刪除它
重命名current目錄為previous.tmp
在DataNode文件中創建current/{bpid}/current目錄, 這裡的bpid是blockpool ID,是HDFS 2.x版本中引入的新術語,用於管理block pool slice
遍歷previous.tmp文件中所有的塊,為這些塊建立硬連接,保存到current/{bpid}/current目錄,這個過程 中,塊文件的名稱可能會因版本跨度而發送變化。回想一下NameNode中塊信息的升級,由於塊信息中塊的文件名主要包戶一個生成的時間戳和文件的序列 號,因此,在變更塊文件名的時候,只要NameNode和DataNode按照相同的規則進行變換,在升級後,仍能保持和HDFS升級前在用戶層面上是的 一致的。
重命名previous.tmp目錄為previous,並在這個時候告訴HDFS,此block pool slice升級完成
硬連接是某些文件系統的一個高級特性,某些文件系統中設計有兩種Node,一種是INode,存儲文件的索引信息,一種是DataBlock,存儲 文件真正的數據,文件的名字是存儲在INode中的,INode還使用一個指針指向此文件的第一個DataBlock,建立硬連接實質上是為文件的數據塊 新建一個INode,這個INode也是指向此文件的第一個數據塊。而這個INode中包含的文件名可以和此文件的原名字不同。當然,原文件名其實也是存 儲該文件DataBlock的硬連接。在這樣的文件系統上,刪除一個文件,只是刪除這個文件的INode,如果一個文件的DataBlock沒有 INode指向它,這些DataBlock就可以被文件系統回收。只要還有一個INode指向這些DataBlock,它們就不會被回收。在這樣的文件系 統中,INode可以被設計得很小,比如Ext4,一個INode占512字節,一個DataBlock至少是4K,在格式化磁盤時,系統分配INode 的個數約為DataBlock個數的1/8。
所以,采用硬連接的方式升級DataNode所需要操作的磁盤空間就小很多了。從邏輯上說,也不會對文件系統造成損壞。
如果升級過程失敗,需要通過rollback命令選項,讓HDFS回滾到升級之前到狀態。由於此時兩個版本的HDFS文件系統信息都是是存在的,回滾的過程其實是一個文件夾重命名的過程,具體的步驟如下面:
把current目錄重名為remove.tmp
把previous目錄重命名為current
刪除remove.tmp目錄
到此時,HDFS升級的細節討論的差不多,對HDFS能成功升級也抱有信心,當然能安全回滾也不是問題,當HDFS程序正確的執行完升級各步驟後,還需要手工的執行
hadoop dfsadmin -upgradeFinallized讓HDFS刪除升級之前到文件,這個命令執行完後,也就不能回滾到以前的HDFS版本了。