最近有位同事經常問一些Hadoop的東西,特別是Hdfs的一些細節,有些記得不清楚,所以趁機整理一波。
會按下面的大綱進行整理:
- 簡單介紹
Hdfs
- 簡單介紹
Hdfs
讀寫流程 - 介紹
Hdfs HA
實現方式 - 介紹
Yarn
統一資源管理器 - 追一下
Hdfs
讀寫的原始碼
同時也有其他方面的整理,有興趣可以看看:
演算法系列-動態規劃(4):買賣股票的最佳時機
資料庫倉庫系列(一)什麼是資料倉儲為什麼要資料倉儲
羅拉的好奇
對話記錄 |
---|
羅拉 八哥,最近我們不是在建立資料倉儲嘛有個叫做Hdfs 的東西,好像很厲害,你給我講講這是啥玩意唄 |
八哥 額,你先說說你對Hdfs瞭解多少? |
羅拉 我只是聽說這個使用分散式儲存的框架,可以儲存海量的資料,在大資料領域很常用 |
八哥 就這?那就有點尷尬,看來我得從頭開始給你介紹了,所以今晚的碗你洗 |
羅拉 如果你說的我都懂了,那沒問題 |
八哥 行,成交 |
查個戶口
要想了解Hdfs,就得先查一下他的戶口。
Hdfs全名叫做Hadoop
分散式檔案系統(Hadoop Distributed File System
)這貨跟Hadoop
還有關係。
那沒辦法,先去看看Hadoop
又是啥玩意。
Hadoop
是一個由Apache
基金會所開發的分散式系統基礎架構。
使用者可以在不瞭解分散式底層細節的情況下,開發分散式程式。充分利用叢集的優勢進行高速運算和儲存。
目前的Hadoop有一個強大的生態系統,如下:
其中有幾個為核心的元件,如下:
- Hdfs(
Hadoop Distributed File System
):可提供高吞吐量的分散式檔案系統 - Yarn:用於任務排程和叢集資源管理的框架(這玩意是2.0後的重大突破)
- MapReduce:基於Yarn上,用於大資料叢集並行處理的系統。(現在更多的被當作思想來看,或者高手才手擼這玩意)
核心元件隨著Hadoop
更新,在1.x
與2.x
有顯著的區別,如下:
從上面兩圖中我們可以發現,Hadoop1.x
和Hadoop2.x
的主要區別就是2.x
引入了Yarn
。
之所以說這是一個大的突破主要是因為以下幾點:
- 在
1.x
中MapReduce
不僅負責資料的計算,還負責叢集作業的排程和資源(記憶體,CPU)管理(自己即是也是工人,全能型人才),擴充性差,應用場景單一。 - 在
2.x
中,引入了Yarn
,負責叢集的資源的統一管理和排程。MapReduce
則執行在Yarn
之上,只負責資料的計算。分工更加明確。 - 由於
Yarn
具有通用性,可以作為其他的計算框架的資源管理系統, 比如(Spark,Strom,SparkStreaming
等)。
而Yarn
作為統一的資源管理和排程,帶來了三個顯著的效益:
- 提高資源利用率:通過統一資源管理和排程,各個不同的元件可以共享叢集資源,提高資源的利用率,避免各自為戰出現資源利用不充分甚至資源緊張的情況。
- 降低運維成本:只需對叢集進行統一的管理,降低工作量。
- 資料共享:共享叢集通過共享叢集之間的資料和資源,有效提高資料移動的效率和降低時間成本
共享叢集資源架構圖:
以後會專門寫一篇Yarn的文章,此處不再詳細展開。
有了上面的介紹,我們就看看Hdfs是到底做了什麼。
Hdfs
Hdfs的設計目標
在大資料我們經常會通過分散式計算對海量資料進行儲存和管理。
Hdfs
就是在這樣的需求下應運而生,它基於流資料模式訪問和能夠處理超大的檔案。
並且可以在在廉價的機器上執行並提供資料容錯機制,給大資料的處理帶來很大便利。
Hdfs
設計之初,就有幾個目標:
目標 | 實現方式或原因 |
---|---|
硬體故障 | 硬體故障是常態 需要有故障檢測,並且快速自動的從故障中恢復的機制 |
流式訪問 | Hdfs 強調的是資料訪問高吞吐量,而不是訪問的低延遲性 |
大型資料集 | 支援大型資料集的檔案系統 為具有數百甚至更多階段的叢集提供資料的儲存與計算 |
簡單一致性 | 一次寫入,多次讀取 一旦檔案建立,寫入,關閉就不能從任意的位置進行改變 ps:在 2.x 後可以在檔案末尾追加內容 |
移動計算比移動資料容易 | 利用資料的本地性,提高計算的效率 |
平臺可移植性 | 使用Java語言構建 任何支援 Java 的計算機都可以執行Hdfs |
Hdfs框架構設計
接下來我們看看Hdfs
叢集的框架
從上圖可以明顯的看出,Hdfs是一個典型的主/從架構。
我們看看它有什麼元件
NameNode
Master
由一個NameNode
組成,是一個主伺服器,主要功能如下:
- 負責管理檔案系統的名稱空間,儲存後設資料(檔名稱、大小、儲存位置等)
- 協調客戶端對檔案的訪問
NameNode
會將所有的檔案和資料夾儲存在一個檔案系統的目錄樹中,並且記錄任何後設資料的變化。
我們知道Hdfs
會將檔案拆分為多個資料塊儲存,其中檔案與檔案塊的對應關係也儲存在檔案系統的目錄樹中,由NameNode
維護。
除了檔案與資料塊的對映資訊,還有一個資料塊與DataNode
的對映資訊,
因為資料塊最終是儲存到DataNode
中。我們需要知道一個檔案資料塊存在那些DataNode
中,
或者說DataNode
中有哪些資料塊。這些資訊也記錄在NameNode
中。
從上圖中可以看到,NameNode
與DataNode
之間還有心跳。
NameNode
會週期性的接收叢集中DataNode的“心跳”和“塊報告”。
通過“心跳”檢測DataNode
的狀態(是否當機),決定是否需要作出相關的調整策略。
“塊報告”包含DataNode上所有資料塊列表的資訊
DataNode
DataNode
是Hdfs
的從節點,一般會有多個DataNode
(一般一個節點一個DataNode
)。
主要功能如下:
- 管理它們所執行節點的資料儲存
- 週期性向NameNode上報自身儲存的“塊資訊”
這裡的管理指的是在Client
對Hdfs
進行資料讀寫操作的時候,會接收來自NameNode
的指令,執行資料塊的建立、刪除、複製等操作。
DataNode
中的資料儲存在本地磁碟。
Blcok
從上圖可以看出,在內部,一個檔案會被切割為多個塊(Blocks
),這些塊儲存在一組Datanodes
中,同時還有對Block
進行備份(Replication)。
Hdfs
檔案以Block
的形式儲存,預設一個Block
大小為128MB
(1.x
為64Mb
)
簡單來說就是一個檔案會被切割為多個128MB
的小檔案進行儲存,如果檔案小於128MB
則不切割,按照實際的大小儲存,不會佔用整個資料塊的大小。
關於
Hdfs
讀寫後續會寫一個原始碼追蹤的文章,到時候就知道如何實現按照實際大小儲存了。
預設Block
之所以是128M
,主要是降低定址開銷和獲得較佳的執行效率。
因為Block
越小,那麼切割的檔案就越多,定址耗費的時間也會越多。
但如果Block
太大,雖然切割的檔案比較少,定址快,但是單個檔案過大,執行時間過長,發揮不了平行計算的優勢。
每個
Block
的後設資料也記錄在NameNode
中,可以說Block
的大小一定程度也會影響整個叢集的儲存能力
同時為了容錯,一般會有三個副本,副本的存放策略一般為:
備份編號 | 位置 |
---|---|
1 | Standalone 模式:上傳檔案的節點Cluster 模式:隨機選一臺記憶體充足的機器 |
2 | 與1號備份同機架的不同節點 |
3 | 不同機架的節點 |
備份除了容錯,也是資料本地性(移動計算)的一個強有力支撐。
Secondary NameNode
有一說一,
Secondary NameNode
取了一個標題黨的的名字,這個讓人感覺這就是第二個NameNode
。
實際上不是,在介紹 Secondary NameNod
e 之前,我們得先了解NameNode
是怎麼儲存後設資料的。
我們之前說的Hdfs
的後設資料資訊主要存在兩個檔案中:fsimage
和edits
。
fsimage
:檔案系統的對映檔案,儲存檔案的後設資料資訊,包括檔案系統所有的目錄、檔案資訊以及資料塊的索引。edits
:Hdfs
操作日誌檔案,記錄Hdfs
對檔案系統的修改日誌。
NameNode
對這兩個檔案的操作如下圖:
從這張圖中,可以知道,在NameNode
啟動的時候,會從fsimage
中讀取Hdfs
的狀態,
同時會合並fsimage
與edits
獲得完整的後設資料資訊,並將新的Hdfs
狀態寫入fsimage
。
並使用一個空的edits
檔案開始正常操作。
但是在產品化的叢集(如Ambari
或ClouderManager
)中NameNode
是很少重啟的,在我的工作場景中,重啟基本就是掛了。
這也意味著當NameNode
執行了很長時間後,edits
檔案會變得很大。
在這種情況下就會兩個問題:
edits
檔案隨著操作增加會變的很大,怎麼去管理這個檔案是一個又是一個問題。NameNode
的重啟會花費很長時間,因為經過長時間執行,Hdfs
會有很多改動(edits
)要合併到fsimage
檔案上。
既然明白了痛點所在,那自然是需要對症下藥,核心問題就是edits
會越來越大,導致重啟操作時間變長,只要解決這個問題就完事了。
我們只需要保證我們fsimage
是最新的,而不是每次啟動的時候才合併出完整的fsimage
就可以了,也就是更新快照。
這就是是我們需要介紹的Secondary NameNode
的工作。
Secondary NameNode
用於幫助NameNode
管理後設資料,從而使得NameNode
可以快速、高效的工作。
簡單的說Secondary NameNode
的工作就是定期合併fsimage
和edits
日誌,將edits
日誌檔案大小控制在一個限度下。
因為記憶體需求和
NameNode
在一個數量級上,所以通常secondary NameNode
和NameNode
執行在不同的機器上。
下面看看這個過程是怎麼發生的
ps:圖中有個虛線,就是在傳輸
edits
的時候會不會傳輸fsimage
?這個在最後面會有相關說明
這些步驟簡單的總結就是:
在Secondary NameNod
e端:
Secondary NameNode
定期到NameNode
更新edits
- 將更新到的
edits
與自身的fsimage
或重新下載的fsimage
合併獲得完整的fsiamge.ckpt
- 將
fsimage.ckpt
傳送給NameNode
在NameNode
端:
- 在
Secondary NameNode
發出合併訊號的時候,將更新日誌寫到一個新的new.edits
中,停用舊的edits
。 - 在
Secondary NameNode
將新的fsimage.ckpt
發過來後,將舊的fsimage
用新的fsimage.ckpt
替換,同時將久的edits
用new.edits
替換。
那麼合併的時機是什麼?主要有兩個引數可以配置:
fs.checkpoint.period
:指定連續兩次檢查點的最大時間間隔, 預設值是1小時。fs.checkpoint.size
:定義了edits
日誌檔案的最大值,一旦超過這個值會導致強制執行檢查點(即使沒到檢查點的最大時間間隔)。預設值是64MB
。
所以,Secondary NameNode
並不是第二個NameNode
的意思,只是NameNode
的一個助手。更準確的理解是它僅僅是NameNode
的一個檢查點(CheckPoint
)。
同時,我們所說的HA,也就是高可用,也不是隻
Secondary NameNode
,詳細的會有專門的文章介紹。
有個坑
在Secondary NameNode
執行合併的時候,有一個步驟3,通過http Get
的方式從NameNode
獲取edits
檔案。
在這一步驟中,到底需不需要把NameNode
中的fsimage
也獲取過來,目前我看了挺多資料,挺矛盾的。
在Hadoop權威指南中說明如下:
從這裡看,應該是同是獲得了fsimage
與edits
。
但是,在官網中有一個描述:
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
,那麼在進行合併的時候不需要從NameNode
把fsimage
也傳輸過來吧?
但是執行合併的時候輸出的日誌:
這看起來好像是需要下載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_165
與 edits_166
合併出來的image_166
)。
與當前Secondary NameNode
中的image_165
不一致了,所以需要重新拉取,
具體以後有時間再看看。
但是隻要記住有些場景下會把fsimage
load下來,有些場景不會就可以了。
後續的內容
“怎麼樣,羅拉,這個簡單的介紹可以吧”?
“還行,但是就這麼簡單?”羅拉狐疑。
“簡單?這都是經過前人的努力才搞出來的,而且這是簡單的介紹,實際上我們現在再生產用的和這個其實都不太一樣了。”
“有多大差別?”
“我現在這裡沒有給你介紹Hdfs讀寫流程,還有現在NameNode其實還存在問題,統一的資源管理Yarn也沒說,早著呢。”
“還有,想真正掌握,還的去追下原始碼看看這個操作是怎麼實現的。就我現在說的這些,去面試都過不了。”八哥無限鄙視
“哦,那就是說你講的不完善,今晚的碗我不洗,等你講完了再說。”
“這麼賴皮的嘛?....”
後面有幾個點會單獨拿出來寫個文章,主要是以下幾個方面的內容:
- Hdfs讀寫流程
- Yarn統一資源管理
- Hdfs HA(高可用)
- Hdfs讀寫原始碼解析(會用3.1.3的原始碼)
本文為原創文莊,轉載請註明出處!!!
歡迎關注【兔八哥雜談】