大數據系列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 當前目錄的截圖如下:

注意,在Secondary 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的源碼)

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM