大數據系列1:一文初識Hdfs

最近有位同事經常問一些Hadoop的東西,特別是Hdfs的一些細節,有些記得不清楚,所以趁機整理一波。

會按下面的大綱進行整理:

  1. 簡單介紹Hdfs
  2. 簡單介紹Hdfs讀寫流程
  3. 介紹Hdfs HA實現方式
  4. 介紹Yarn統一資源管理器
  5. 追一下Hdfs讀寫的源碼

同時也有其他方面的整理,有興趣可以看看:
算法系列-動態規劃(4):買賣股票的最佳時機
數據庫倉庫系列(一)什麼是數據倉庫爲什麼要數據倉庫


羅拉的好奇

對話記錄

羅拉

八哥,最近我們不是在建立數據倉庫嘛
有個叫做Hdfs 的東西,好像很厲害,你給我講講這是啥玩意唄

八哥

額,你先說說你對Hdfs瞭解多少?

羅拉

我只是聽說這個使用分佈式存儲的框架,可以存儲海量的數據,在大數據領域很常用

八哥

就這?那就有點尷尬,看來我得從頭開始給你介紹了,所以今晚的碗你洗

羅拉

如果你說的我都懂了,那沒問題

八哥

行,成交


查個戶口

要想了解Hdfs,就得先查一下他的戶口。
Hdfs全名叫做Hadoop分佈式文件系統(Hadoop Distributed File System)這貨跟Hadoop還有關係。
那沒辦法,先去看看Hadoop又是啥玩意。

Hadoop是一個由Apache基金會所開發的分佈式系統基礎架構
用戶可以在不瞭解分佈式底層細節的情況下,開發分佈式程序。充分利用集羣的優勢進行高速運算和存儲。

目前的Hadoop有一個強大的生態系統,如下:

Hadoop生態

其中有幾個爲核心的組件,如下:

  1. Hdfs(Hadoop Distributed File System):可提供高吞吐量的分佈式文件系統
  2. Yarn:用於任務調度和集羣資源管理的框架(這玩意是2.0後的重大突破)
  3. MapReduce:基於Yarn上,用於大數據集羣並行處理的系統。(現在更多的被當作思想來看,或者高手才手擼這玩意)

核心組件隨着Hadoop更新,在1.x2.x有顯著的區別,如下:

從上面兩圖中我們可以發現,Hadoop1.xHadoop2.x的主要區別就是2.x引入了Yarn
之所以說這是一個大的突破主要是因爲以下幾點:

  1. 1.xMapReduce不僅負責數據的計算,還負責集羣作業的調度和資源(內存,CPU)管理(自己即是也是工人,全能型人才),拓展性差,應用場景單一。
  2. 2.x中,引入了Yarn,負責集羣的資源的統一管理和調度。 MapReduce則運行在Yarn之上,只負責數據的計算。分工更加明確。
  3. 由於Yarn具有通用性,可以作爲其他的計算框架的資源管理系統, 比如(Spark,Strom,SparkStreaming等)。

Yarn作爲統一的資源管理和調度,帶來了三個顯著的效益:

  1. 提高資源利用率:通過統一資源管理和調度,各個不同的組件可以共享集羣資源,提高資源的利用率,避免各自爲戰出現資源利用不充分甚至資源緊張的情況。
  2. 降低運維成本:只需對集羣進行統一的管理,降低工作量。
  3. 數據共享:共享集羣通過共享集羣之間的數據和資源,有效提高數據移動的效率和降低時間成本

共享集羣資源架構圖:

以後會專門寫一篇Yarn的文章,此處不再詳細展開。

有了上面的介紹,我們就看看Hdfs是到底做了什麼。


Hdfs

Hdfs的設計目標

在大數據我們經常會通過分佈式計算對海量數據進行存儲和管理。
Hdfs就是在這樣的需求下應運而生,它基於流數據模式訪問和能夠處理超大的文件。
並且可以在在廉價的機器上運行並提供數據容錯機制,給大數據的處理帶來很大便利。

Hdfs設計之初,就有幾個目標:

目標 實現方式或原因
硬件故障 硬件故障是常態
需要有故障檢測,並且快速自動的從故障中恢復的機制
流式訪問 Hdfs強調的是數據訪問高吞吐量,而不是訪問的低延遲性
大型數據集 支持大型數據集的文件系統
爲具有數百甚至更多階段的集羣提供數據的存儲與計算
簡單一致性 一次寫入,多次讀取
一旦文件建立,寫入,關閉就不能從任意的位置進行改變
ps:在2.x後可以在文件末尾追加內容
移動計算比移動數據容易 利用數據的本地性,提高計算的效率
平臺可移植性 使用Java語言構建
任何支持Java的計算機都可以運行Hdfs

Hdfs框架構設計

接下來我們看看Hdfs集羣的框架

從上圖可以明顯的看出,Hdfs是一個典型的主/從架構。

我們看看它有什麼組件


NameNode

Master由一個NameNode組成,是一個主服務器,主要功能如下:

  1. 負責管理文件系統的命名空間,存儲元數據(文件名稱、大小、存儲位置等)
  2. 協調客戶端對文件的訪問

NameNode會將所有的文件和文件夾存儲在一個文件系統的目錄樹中,並且記錄任何元數據的變化。

我們知道Hdfs會將文件拆分爲多個數據塊保存,其中文件與文件塊的對應關係也存儲在文件系統的目錄樹中,由NameNode維護。

除了文件與數據塊的映射信息,還有一個數據塊與DataNode 的映射信息,
因爲數據塊最終是存儲到DataNode中。我們需要知道一個文件數據塊存在那些DataNode中,
或者說DataNode中有哪些數據塊。這些信息也記錄在NameNode中。

從上圖中可以看到,NameNodeDataNode之間還有心跳。
NameNode會週期性的接收集羣中DataNode的“心跳”和“塊報告”。
通過“心跳”檢測DataNode的狀態(是否宕機),決定是否需要作出相關的調整策略。
“塊報告”包含DataNode上所有數據塊列表的信息


DataNode

DataNodeHdfs的從節點,一般會有多個DataNode(一般一個節點一個DataNode)。
主要功能如下:

  1. 管理它們所運行節點的數據存儲
  2. 週期性向NameNode上報自身存儲的“塊信息”

這裏的管理指的是在ClientHdfs進行數據讀寫操作的時候,會接收來自NameNode的指令,執行數據塊的創建、刪除、複製等操作。

DataNode中的數據保存在本地磁盤。


Blcok

從上圖可以看出,在內部,一個文件會被切割爲多個塊(Blocks),這些塊存儲在一組Datanodes中,同時還有對Block進行備份(Replication)。

Hdfs文件以Block的形式存儲,默認一個Block大小爲128MB1.x64Mb

簡單來說就是一個文件會被切割爲多個128MB的小文件進行儲存,如果文件小於128MB則不切割,按照實際的大小存儲,不會佔用整個數據塊的大小。

關於Hdfs讀寫後續會寫一個源碼追蹤的文章,到時候就知道如何實現按照實際大小存儲了。

默認Block之所以是128M,主要是降低尋址開銷和獲得較佳的執行效率。
因爲Block越小,那麼切割的文件就越多,尋址耗費的時間也會越多。
但如果Block太大,雖然切割的文件比較少,尋址快,但是單個文件過大,執行時間過長,發揮不了並行計算的優勢。

每個Block的元數據也記錄在NameNode中,可以說Block的大小一定程度也會影響整個集羣的存儲能力

同時爲了容錯,一般會有三個副本,副本的存放策略一般爲:

備份編號 位置
1 Standalone模式:上傳文件的節點
Cluster模式:隨機選一臺內存充足的機器
2 與1號備份同機架的不同節點
3 不同機架的節點

備份除了容錯,也是數據本地性(移動計算)的一個強有力支撐。


Secondary NameNode

有一說一,
Secondary NameNode 取了一個標題黨的的名字,這個讓人感覺這就是第二個NameNode
實際上不是,在介紹 Secondary NameNode 之前,我們得先了解NameNode是怎麼存儲元數據的。

我們之前說的Hdfs的元數據信息主要存在兩個文件中:fsimageedits

  1. fsimage:文件系統的映射文件,存儲文件的元數據信息,包括文件系統所有的目錄、文件信息以及數據塊的索引。
  2. editsHdfs操作日誌文件,記錄Hdfs對文件系統的修改日誌。

NameNode 對這兩個文件的操作如下圖:

從這張圖中,可以知道,在NameNode啓動的時候,會從fsimage中讀取Hdfs的狀態,
同時會合並fsimageedits獲得完整的元數據信息,並將新的Hdfs狀態寫入fsimage
並使用一個空的edits文件開始正常操作。

但是在產品化的集羣(如AmbariClouderManager)中NameNode是很少重啓的,在我的工作場景中,重啓基本就是掛了。
這也意味着當NameNode運行了很長時間後,edits文件會變得很大。

在這種情況下就會兩個問題:

  1. edits文件隨着操作增加會變的很大,怎麼去管理這個文件是一個又是一個問題。
  2. NameNode的重啓會花費很長時間,因爲經過長時間運行,Hdfs會有很多改動(edits)要合併到fsimage文件上。

既然明白了痛點所在,那自然是需要對症下藥,核心問題就是edits會越來越大,導致重啓操作時間變長,只要解決這個問題就完事了。

我們只需要保證我們fsimage是最新的,而不是每次啓動的時候才合併出完整的fsimage就可以了,也就是更新快照。
這就是是我們需要介紹的Secondary NameNode的工作。

Secondary NameNode 用於幫助NameNode管理元數據,從而使得NameNode可以快速、高效的工作。

簡單的說Secondary NameNode的工作就是定期合併fsimageedits日誌,將edits日誌文件大小控制在一個限度下。

因爲內存需求和NameNode在一個數量級上,所以通常secondary NameNodeNameNode 運行在不同的機器上。

下面看看這個過程是怎麼發生的

ps:圖中有個虛線,就是在傳輸edits的時候會不會傳輸fsimage?這個在最後面會有相關說明

這些步驟簡單的總結就是:
Secondary NameNode端:

  1. Secondary NameNode定期到NameNode更新edits
  2. 將更新到的edits與自身的fsimage或重新下載的fsimage合併獲得完整的fsiamge.ckpt
  3. fsimage.ckpt發送給NameNode

NameNode端:

  1. Secondary NameNode發出合併信號的時候,將更新日誌寫到一個新的new.edits中,停用舊的edits
  2. Secondary NameNode將新的fsimage.ckpt發過來後,將舊的fsimage用新的fsimage.ckpt替換,同時將久的editsnew.edits替換。

那麼合併的時機是什麼?主要有兩個參數可以配置:

  1. fs.checkpoint.period:指定連續兩次檢查點的最大時間間隔, 默認值是1小時。
  2. fs.checkpoint.size:定義了edits日誌文件的最大值,一旦超過這個值會導致強制執行檢查點(即使沒到檢查點的最大時間間隔)。默認值是64MB

所以,Secondary NameNode 並不是第二個NameNode的意思,只是NameNode的一個助手。更準確的理解是它僅僅是NameNode的一個檢查點(CheckPoint)。

同時,我們所說的HA,也就是高可用,也不是隻Secondary NameNode,詳細的會有專門的文章介紹。


有個坑

Secondary NameNode 執行合併的時候,有一個步驟3,通過http Get的方式從NameNode獲取edits文件。

在這一步驟中,到底需不需要把NameNode中的fsimage也獲取過來,目前我看了挺多資料,挺矛盾的。


在Hadoop權威指南中說明如下:

從這裏看,應該是同是獲得了fsimageedits


但是,在官網中有一個描述:

The secondary NameNode stores the latest checkpoint in a directory which is structured the same way as the primary NameNode’s directory.
So that the check pointed image is always ready to be read by the primary NameNode if necessary.

Secondary NameNode將最新的檢查點存儲在與主NameNode目錄結構相同的目錄中。
所以在NameNode需要的時候,會去讀取檢查點的鏡像image

並且在Secondary NameNode in Hadoop中關於Secondary NameNode有這樣的一個描述:

NameNode當前目錄的截圖如下:

fsimage 的當前版本號位165,從最後一個檢查點fsimage165正在進行的edits_inprogress日誌編號爲166,
在下次namenode重啓時,它將與fsimage165合併,fsimage166將被創建。

Secondary NameNode 當前目錄的截圖如下:

注意,在namenode中沒有對應的實時編輯edits_inprogress_166版本。
此時有fsimage165,那麼在進行合併的時候不需要從NameNodefsimage也傳輸過來吧?

但是執行合併的時候輸出的日誌:

這看起來好像是需要下載fsimage

瞬間蒙圈了。

沒辦法只能去老老實實去瞄一下源碼了:
下面只展示核心代碼:

//org.apache.Hadoop.hdfs.server.namenode.SecondaryNameNode#doCheckpoint
 /**
   * 創建一個新的檢查點
   * @return image 是否從NameNode獲取
   */
  @VisibleForTesting
  @SuppressWarnings("deprecated")
  public boolean doCheckpoint() throws IOException {
    //告訴namenode在一個新的編輯文件中開始記錄事務,將返回一個用於上傳合併後的image的token。
    CheckpointSignature sig = namenode.rollEditLog();
    //是否重新加載fsimage
    boolean loadImage = false;    
    //這裏需要reload fsImage有兩種情況
    //1. downloadCheckpointFiles中判斷fsiamge變化情況
    //2. 是否發生checkpointImage 和並錯誤
    loadImage |= downloadCheckpointFiles(
        fsName, checkpointImage, sig, manifest) |
        checkpointImage.hasMergeError(); 

      //執行合併操作    
      doMerge(sig, manifest, loadImage, checkpointImage, namesystem);

}

// org.apache.Hadoop.hdfs.server.namenode.SecondaryNameNode#downloadCheckpointFiles

  /**
   * 從name-node 下載 fsimage 和 edits
   * @return true if a new image has been downloaded and needs to be loaded
   * @throws IOException
   */
  static boolean downloadCheckpointFiles(...) throws IOException {
  	        //根據Image的變化情況決定是否download image
            if (sig.mostRecentCheckpointTxId ==
                dstImage.getStorage().getMostRecentCheckpointTxId()) {
              LOG.info("Image has not changed. Will not download image.");
            } else {
              LOG.info("Image has changed. Downloading updated image from NN.");
              MD5Hash downloadedHash = TransferFsImage.downloadImageToStorage(
                  nnHostPort, sig.mostRecentCheckpointTxId,
                  dstImage.getStorage(), true, false);
              dstImage.saveDigestAndRenameCheckpointImage(NameNodeFile.IMAGE,
                  sig.mostRecentCheckpointTxId, downloadedHash);
            }
        
            // download edits
            for (RemoteEditLog log : manifest.getLogs()) {
              TransferFsImage.downloadEditsToStorage(
                  nnHostPort, log, dstImage.getStorage());
            }
            // true if we haven't loaded all the transactions represented by the downloaded fsimage.
            return dstImage.getLastAppliedTxId() < sig.mostRecentCheckpointTxId;
  }            


  // org.apache.Hadoop.hdfs.server.namenode.SecondaryNameNode#doMerge
  void doMerge(...) throws IOException {   
  	  //如果需要load iamge 就reload image
      if (loadImage) dstImage.reloadFromImageFile(file, dstNamesystem);

    Checkpointer.rollForwardByApplyingLogs(manifest, dstImage, dstNamesystem);
    // 清除舊的fsimages 和edits 
    dstImage.saveFSImageInAllDirs(dstNamesystem, dstImage.getLastAppliedTxId());      
}

從上面的核心代碼可以看到,進行合併的時候,是否需要從NameNode load fsiamge是要看情況的。

不過目前fsiamge 是否改變這點沒有深入看源碼,
猜測大概是初次啓動NameNode時,合併出新的fsiamge(如上面的image_165edits_166 合併出來的image_166)。
與當前Secondary NameNode 中的image_165不一致了,所以需要重新拉取,
具體以後有時間再看看。

但是隻要記住有些場景下會把fsimage load下來,有些場景不會就可以了。


後續的內容

“怎麼樣,羅拉,這個簡單的介紹可以吧”?

“還行,但是就這麼簡單?”羅拉狐疑。

“簡單?這都是經過前人的努力才搞出來的,而且這是簡單的介紹,實際上我們現在再生產用的和這個其實都不太一樣了。”

“有多大差別?”

“我現在這裏沒有給你介紹Hdfs讀寫流程,還有現在NameNode其實還存在問題,統一的資源管理Yarn也沒說,早着呢。”
“還有,想真正掌握,還的去追下源碼看看這個操作是怎麼實現的。就我現在說的這些,去面試都過不了。”八哥無限鄙視

“哦,那就是說你講的不完善,今晚的碗我不洗,等你講完了再說。”

“這麼賴皮的嘛?....”

後面有幾個點會單獨拿出來寫個文章,主要是以下幾個方面的內容:

  1. Hdfs讀寫流程
  2. Yarn統一資源管理
  3. Hdfs HA(高可用)
  4. Hdfs讀寫源碼解析(會用3.1.3的源碼)

本文爲原創文莊,轉載請註明出處!!!
歡迎關注【兔八哥雜談】

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章