仿照原始碼|HDFS之DataNode:啟動過程,NameNode也從啟動過程開始。
namenode的啟動過程與HA緊緊繫結在一起,但本文暫不討論HA相關內容,以後再填HA的坑。
原始碼版本:Apache Hadoop 2.6.0
可參考猴子追原始碼時的速記打斷點,親自debug一遍。
開始之前
總覽
namenode的啟動過程圍繞著safemode、HA等展開,啟動之後,各種工作執行緒開始發揮作用。主要包括:
- 載入fsimage與editlog
- 啟動多種工作執行緒,主要包括:
- 通訊:RpcServer
- 監控:JVMPauseMonitor、PendingReplicationMonitor、DecommissionManager#Monitor、HeartbeatManager#Monitor、ReplicationMonitor、LeaseManager#Monitor、NameNodeResourceMonitor
- 其他:HttpServer (Web UI)、NameNodeEditLogRoller
- HA相關(暫不討論)
- 關閉safemode
CacheManager#CacheReplicationMonitor等暫不討論。
檔案管理機制
namenode通過FSNamesystem管理檔案後設資料。具體來講:
- 通過FSDirectory管理檔案系統的名稱空間
- 通過BlockManager管理檔案到資料塊的對映和資料塊到資料節點的對映
namenode上的資料塊狀態
與yarn不同,hdfs並沒有直接用狀態機來管理block,而是將不同狀態的block儲存在不同的緩衝區中,狀態遷移則對應資料塊在不同緩衝區中的移動。包括BlockManager#blocksMap,namenode上的資料塊狀態共涉及以下幾種緩衝區:
正在寫入
的資料塊:通過LeaseManager掃描INodeFile,BlockInfo#isComplete()返回false即為正在寫入的資料塊(不常用)- 儲存資料塊的元資訊,可以認為儲存
已完成
等所有狀態的資料塊:BlockManager#blocksMap,BlockInfo#isComplete()返回true 需要複製
的資料塊:BlockManager#neededReplications正在複製
的資料塊:BlockManager#pendingReplications複製超時
的資料塊:BlockManager#pendingReplications#timedOutItems- 多餘的資料塊:BlockManager#excessReplicateMap(即
需要刪除
的資料塊) - 無效資料塊緩衝區BlockManager#invalidateBlocks(即
正在刪除
的資料塊)
BlockManager#excessReplicateMap與BlockManager#neededReplications對應,BlockManager#invalidateBlocks與BlockManager#pendingReplications對應,為了統一,本文相應稱BlockManager#excessReplicateMap為“需要刪除資料塊緩衝區”,稱BlockManager#invalidateBlocks為“正在刪除資料塊緩衝區”。
其中,與資料塊(對應多個副本)寫入密切相關的LeaseManager;與副本複製任務密切相關的是BlockManager#neededReplications、BlockManager#pendingReplications、BlockManager#pendingReplications#timedOutItems,與副本刪除密切相關的是BlockManager#excessReplicateMap、BlockManager#invalidateBlocks。
詳細的狀態轉換邏輯在分析完原始碼後,再來總結。
文章的組織結構
- 如果只涉及單個分支的分析,則放在同一節。
- 如果涉及多個分支的分析,則在下一級分多個節,每節討論一個分支。
- 多執行緒的分析同多分支。
- 每一個分支和執行緒的組織結構遵循規則1-3。
主流程
namenode的Main Class是NameNode,先找NameNode.main():
public static void main(String argv[]) throws Exception {
if (DFSUtil.parseHelpArgument(argv, NameNode.USAGE, System.out, true)) {
System.exit(0);
}
try {
StringUtils.startupShutdownMessage(NameNode.class, argv, LOG);
// 建立namenode
NameNode namenode = createNameNode(argv, null);
// 等待namenode關閉
if (namenode != null) {
namenode.join();
}
} catch (Throwable e) {
LOG.fatal("Failed to start namenode.", e);
terminate(1, e);
}
}
...
public void join() {
try {
// 等待RPCServer關閉,其他守護程式會自動關閉
rpcServer.join();
} catch (InterruptedException ie) {
LOG.info("Caught interrupted exception ", ie);
}
}
複製程式碼
NameNode#join()等待namenode關閉,基本邏輯同datanode。
主要看NameNode.createNameNode():
public static NameNode createNameNode(String argv[], Configuration conf)
throws IOException {
LOG.info("createNameNode " + Arrays.asList(argv));
if (conf == null)
conf = new HdfsConfiguration();
// 解析啟動選項
StartupOption startOpt = parseArguments(argv);
if (startOpt == null) {
printUsage(System.err);
return null;
}
setStartupOption(conf, startOpt);
switch (startOpt) {
...// 其他分支
default: {
// 正常啟動的話
// 初始化metric系統
DefaultMetricsSystem.initialize("NameNode");
// 建立NameNode
return new NameNode(conf);
}
}
}
複製程式碼
正常啟動的話,滿足startOpt == StartupOption.REGULAR
,會走到default分支。
NameNode.<init>()
:
public NameNode(Configuration conf) throws IOException {
this(conf, NamenodeRole.NAMENODE);
}
...
protected NameNode(Configuration conf, NamenodeRole role)
throws IOException {
this.conf = conf;
this.role = role;
// 設定NameNode#clientNamenodeAddress為"hdfs://localhost:9000"
setClientNamenodeAddress(conf);
String nsId = getNameServiceId(conf);
String namenodeId = HAUtil.getNameNodeId(conf, nsId);
// HA相關
this.haEnabled = HAUtil.isHAEnabled(conf, nsId);
state = createHAState(getStartupOption(conf));
this.allowStaleStandbyReads = HAUtil.shouldAllowStandbyReads(conf);
this.haContext = createHAContext();
try {
initializeGenericKeys(conf, nsId, namenodeId);
// 完成實際的初始化工作
initialize(conf);
// HA相關
try {
haContext.writeLock();
state.prepareToEnterState(haContext);
state.enterState(haContext);
} finally {
haContext.writeUnlock();
}
} catch (IOException e) {
this.stop();
throw e;
} catch (HadoopIllegalArgumentException e) {
this.stop();
throw e;
}
}
複製程式碼
這裡要特別說明部分HA的內容:
儘管本地的偽分散式叢集無法開啟HA(對應NameNode#haEnabled為false),namenode仍然擁有一個HAState,此時,namenode會被標記為active(對應HAState ACTIVE_STATE = new ActiveState()
),然後在ActiveState#enterState()中啟動LeaseManager#Monitor、NameNodeEditLogRoller等。
具體來講,在NameNode#initialize()完成實際的初始化工作返回後,還要執行ActiveState#enterState(),完成一些只有active狀態namenode才應該做的工作,如:
- 開啟FsDirectory的quota檢查
- 啟動LeaseManager#Monitor
- 啟動NameNodeResourceMonitor
- 啟動NameNodeEditLogRoller
- 啟動CacheManager#CacheReplicationMonitor等
後面會專門討論HA機制,讀者知道何時啟動了這些工作執行緒即可。
下面繼續看NameNode#initialize():
protected void initialize(Configuration conf) throws IOException {
if (conf.get(HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS) == null) {
String intervals = conf.get(DFS_METRICS_PERCENTILES_INTERVALS_KEY);
if (intervals != null) {
conf.set(HADOOP_USER_GROUP_METRICS_PERCENTILES_INTERVALS,
intervals);
}
}
UserGroupInformation.setConfiguration(conf);
loginAsNameNodeUser(conf);
// 初始化metric
NameNode.initMetrics(conf, this.getRole());
StartupProgressMetrics.register(startupProgress);
// 啟動httpServer
if (NamenodeRole.NAMENODE == role) {
startHttpServer(conf);
}
this.spanReceiverHost = SpanReceiverHost.getInstance(conf);
// 從`${dfs.namenode.name.dir}`目錄載入fsimage與editlog,初始化FsNamesystem、FsDirectory、LeaseManager等
loadNamesystem(conf);
// 建立RpcServer,封裝了NameNodeRpcServer#clientRpcServer,支援ClientNamenodeProtocol、DatanodeProtocolPB等協議
rpcServer = createRpcServer(conf);
if (clientNamenodeAddress == null) {
// This is expected for MiniDFSCluster. Set it now using
// the RPC server`s bind address.
clientNamenodeAddress =
NetUtils.getHostPortString(rpcServer.getRpcAddress());
LOG.info("Clients are to use " + clientNamenodeAddress + " to access"
+ " this namenode/service.");
}
if (NamenodeRole.NAMENODE == role) {
httpServer.setNameNodeAddress(getNameNodeAddress());
httpServer.setFSImage(getFSImage());
}
// 啟動JvmPauseMonitor等,反向監控JVM
pauseMonitor = new JvmPauseMonitor(conf);
pauseMonitor.start();
metrics.getJvmMetrics().setPauseMonitor(pauseMonitor);
// 啟動多項重要的工作執行緒
startCommonServices(conf);
}
複製程式碼
實際上,NameNode#loadNamesystem()非常重要,但限於篇幅和精力,猴子只是大概追蹤了下流程,很多細節來不及分析,就不在此處展開了。
當前namenode的角色為NamenodeRole.NAMENODE
,則此處啟動HttpServer;JvmPauseMonitor也在此處啟動。
重頭戲是NameNode#startCommonServices():
private void startCommonServices(Configuration conf) throws IOException {
// 建立NameNodeResourceChecker、啟用BlockManager等
namesystem.startCommonServices(conf, haContext);
registerNNSMXBean();
// 角色非`NamenodeRole.NAMENODE`的在此處啟動HttpServer
if (NamenodeRole.NAMENODE != role) {
startHttpServer(conf);
httpServer.setNameNodeAddress(getNameNodeAddress());
httpServer.setFSImage(getFSImage());
}
// 啟動RPCServer
rpcServer.start();
...// 啟動各外掛
LOG.info(getRole() + " RPC up at: " + rpcServer.getRpcAddress());
if (rpcServer.getServiceRpcAddress() != null) {
LOG.info(getRole() + " service RPC up at: "
+ rpcServer.getServiceRpcAddress());
}
}
複製程式碼
RPCServer的啟動很簡單,重點是FSNamesystem#startCommonServices():
void startCommonServices(Configuration conf, HAContext haContext) throws IOException {
this.registerMBean(); // register the MBean for the FSNamesystemState
writeLock();
this.haContext = haContext;
try {
// 建立NameNodeResourceChecker,並立即檢查一次
nnResourceChecker = new NameNodeResourceChecker(conf);
checkAvailableResources();
assert safeMode != null && !isPopulatingReplQueues();
// 設定一些啟動過程中的資訊
StartupProgress prog = NameNode.getStartupProgress();
prog.beginPhase(Phase.SAFEMODE);
prog.setTotal(Phase.SAFEMODE, STEP_AWAITING_REPORTED_BLOCKS,
getCompleteBlocksTotal());
// 設定已完成的資料塊總量
setBlockTotal();
// 啟用BlockManager
blockManager.activate(conf);
} finally {
writeUnlock();
}
registerMXBean();
DefaultMetricsSystem.instance().register(this);
snapshotManager.registerMXBean();
}
複製程式碼
提醒一下,此時的工作是任何角色的namenode(對於HA來說,即active與standby)都需要做的,“common”即此意。
NameNodeResourceChecker負責檢查磁碟資源。active狀態的namenod會啟動一個監控執行緒NameNodeResourceMonitor,定期執行NameNodeResourceChecker#hasAvailableDiskSpace()檢查可用的磁碟資源。
FSNamesystem#setBlockTotal()設定已完成的資料塊總量completeBlocksTotal = blocksTotal - numUCBlocks
。其中,blocksTotal來自BlockManager,numUCBlocks來自LeaseManager,均從備份中恢復而來。
注意,儘管此時BlockManager#blocksMap從備份中“恢復”了全部資料塊,但這些資料塊的BlockInfo#triplets是空的,因為datanode還沒有將資料塊的資訊彙報到namenode(猴子只啟動了namenode)。
BlockManager#activate()所謂的啟用BlockManager,主要完成了PendingReplicationMonitor、DecommissionManager#Monitor、HeartbeatManager#Monitor、ReplicationMonitor等工作執行緒的啟動:
public void activate(Configuration conf) {
// 啟動PendingReplicationMonitor
pendingReplications.start();
// 啟用DatanodeManager:啟動DecommissionManager#Monitor、HeartbeatManager#Monitor
datanodeManager.activate(conf);
// 啟動BlockManager#ReplicationMonitor
this.replicationThread.start();
}
複製程式碼
啟動PendingReplicationMonitor:PendingReplicationBlocks#start()
PendingReplicationBlocks#start()啟動PendingReplicationMonitor:
void start() {
timerThread = new Daemon(new PendingReplicationMonitor());
timerThread.start();
}
複製程式碼
通過PendingReplicationBlocks#timerThread持有實際的PendingReplicationMonitor執行緒。
PendingReplicationMonitor執行緒後文單獨用一個分支分析。
啟用DatanodeManager:DatanodeManager#activate()
DatanodeManager#activate()啟動DecommissionManager#Monitor、HeartbeatManager#Monitor:
void activate(final Configuration conf) {
final DecommissionManager dm = new DecommissionManager(namesystem, blockManager);
this.decommissionthread = new Daemon(dm.new Monitor(
conf.getInt(DFSConfigKeys.DFS_NAMENODE_DECOMMISSION_INTERVAL_KEY,
DFSConfigKeys.DFS_NAMENODE_DECOMMISSION_INTERVAL_DEFAULT),
conf.getInt(DFSConfigKeys.DFS_NAMENODE_DECOMMISSION_NODES_PER_INTERVAL_KEY,
DFSConfigKeys.DFS_NAMENODE_DECOMMISSION_NODES_PER_INTERVAL_DEFAULT)));
// 啟動DecommissionManager#Monitor
decommissionthread.start();
// 啟用HeartbeatManager
heartbeatManager.activate(conf);
}
複製程式碼
Decommission指datanode的下線操作,暫不關注。主要看HeartbeatManager相關。
HeartbeatManager#activate():
void activate(Configuration conf) {
heartbeatThread.start();
}
複製程式碼
HeartbeatManager#heartbeatThread是一個HeartbeatManager#Monitor執行緒。
HeartbeatManager#Monitor執行緒後文單獨用一個分支分析。
啟動ReplicationMonitor
BlockManager#replicationThread是一個BlockManager#ReplicationMonitor執行緒。
BlockManager#ReplicationMonitor執行緒後文單獨用一個分支分析。
下面分別分析PendingReplicationMonitor執行緒、HeartbeatManager#Monitor執行緒、BlockManager#ReplicationMonitor執行緒,三者與namenode上資料塊的狀態管理密切相關。理解了這三個執行緒的互動關係,有助於以後理解其他資料塊狀態轉換邏輯。
PendingReplicationMonitor執行緒
PendingReplicationMonitor#run():
public void run() {
while (fsRunning) {
long period = Math.min(DEFAULT_RECHECK_INTERVAL, timeout);
try {
// 檢查正在複製的資料塊是否超時
pendingReplicationCheck();
Thread.sleep(period);
} catch (InterruptedException ie) {
if(LOG.isDebugEnabled()) {
LOG.debug("PendingReplicationMonitor thread is interrupted.", ie);
}
}
}
}
複製程式碼
定期執行PendingReplicationMonitor#pendingReplicationCheck()檢查正在複製的資料塊是否超時:
void pendingReplicationCheck() {
synchronized (pendingReplications) {
Iterator<Map.Entry<Block, PendingBlockInfo>> iter =
pendingReplications.entrySet().iterator();
long now = now();
if(LOG.isDebugEnabled()) {
LOG.debug("PendingReplicationMonitor checking Q");
}
// 遍歷`正在複製資料塊緩衝區`
while (iter.hasNext()) {
Map.Entry<Block, PendingBlockInfo> entry = iter.next();
PendingBlockInfo pendingBlock = entry.getValue();
// 如果資料塊超時,則將其新增到`複製超時資料塊緩衝區`
if (now > pendingBlock.getTimeStamp() + timeout) {
Block block = entry.getKey();
synchronized (timedOutItems) {
timedOutItems.add(block);
}
LOG.warn("PendingReplicationMonitor timed out " + block);
iter.remove();
}
}
}
}
複製程式碼
遍歷正在複製資料塊緩衝區BlockManager#pendingReplications
,如果發現有資料塊超時,則將資料塊新增到複製超時資料塊緩衝區BlockManager#pendingReplications#timedOutItems
。
超時指超過
${dfs.namenode.replication.pending.timeout-sec}
毫秒(儘管字尾是“sec”,但程式碼邏輯是毫秒)仍沒有收到副本複製成功的響應。
HeartbeatManager#Monitor執行緒
HeartbeatManager#Monitor#run():
public void run() {
while(namesystem.isRunning()) {
try {
final long now = Time.now();
if (lastHeartbeatCheck + heartbeatRecheckInterval < now) {
// 根據心跳資訊檢查節點與資料目錄
heartbeatCheck();
lastHeartbeatCheck = now;
}
...// 安全認證相關,暫時忽略
} catch (Exception e) {
LOG.error("Exception while checking heartbeat", e);
}
try {
Thread.sleep(5000); // 5 seconds
} catch (InterruptedException ie) {
}
}
}
複製程式碼
定期執行HeartbeatManager#heartbeatCheck()根據心跳資訊檢查節點與資料目錄:
void heartbeatCheck() {
final DatanodeManager dm = blockManager.getDatanodeManager();
// 如果處於啟動過程中的safemode狀態,則不進行檢查(空檢查)
if (namesystem.isInStartupSafeMode()) {
return;
}
// 下述邏輯保證每次只移除一個datanode或資料目錄
boolean allAlive = false;
while (!allAlive) {
// 記錄第一個死亡的datanode
DatanodeID dead = null;
// 記錄非死亡datanode上的第一個失敗資料目錄
DatanodeStorageInfo failedStorage = null;
// 檢查節點與資料目錄
int numOfStaleNodes = 0;
int numOfStaleStorages = 0;
synchronized(this) {
for (DatanodeDescriptor d : datanodes) {
// 檢查節點是否死亡或不新鮮
if (dead == null && dm.isDatanodeDead(d)) {
stats.incrExpiredHeartbeats();
dead = d;
}
if (d.isStale(dm.getStaleInterval())) {
numOfStaleNodes++;
}
// 如果節點儲存且新鮮,則檢查節點上的所有資料目錄是否不新鮮或失敗
DatanodeStorageInfo[] storageInfos = d.getStorageInfos();
for(DatanodeStorageInfo storageInfo : storageInfos) {
if (storageInfo.areBlockContentsStale()) {
numOfStaleStorages++;
}
if (failedStorage == null &&
storageInfo.areBlocksOnFailedStorage() &&
d != dead) {
failedStorage = storageInfo;
}
}
}
dm.setNumStaleNodes(numOfStaleNodes);
dm.setNumStaleStorages(numOfStaleStorages);
}
// 更新allAlive,如果全部通過了檢查,則退出迴圈
allAlive = dead == null && failedStorage == null;
if (dead != null) {
...// 移除第一個死亡資料節點及與其關聯的副本
}
if (failedStorage != null) {
...// 移除第一個失敗資料目錄關聯的副本
}
}
}
複製程式碼
在datanode、資料目錄兩個粒度進行檢查,以確定其是否死亡或不新鮮。如果datanode死亡,則移除該datanode,且不再需要刪除該datanode上的副本,從需要刪除資料塊緩衝區BlockManager#excessReplicateMap
、正在刪除資料塊緩衝區BlockManager#invalidateBlocks
中移除相關副本。如果有資料目錄失敗,則只需要移除與其關聯的副本。
- 不新鮮是介於存活與死亡之間的一個狀態。不新鮮與死亡狀態都通過心跳間隔判斷,閾值不同。
- 猴子有時候說“副本”,有時候說“資料塊”,可能會讓讀者感到糊塗。與資料塊相比,副本與具體的某個datanode繫結。可以理解為,資料塊一個抽象概念,對應多個副本,每個副本與一個datanode繫結。
BlockManager#ReplicationMonitor執行緒
BlockManager#ReplicationMonitor#run():
public void run() {
while (namesystem.isRunning()) {
try {
// Process replication work only when active NN is out of safe mode.
if (namesystem.isPopulatingReplQueues()) {
// 計算副本複製與副本刪除任務
computeDatanodeWork();
// 處理過期的副本複製任務
processPendingReplications();
}
Thread.sleep(replicationRecheckInterval);
} catch (Throwable t) {
...// 異常處理
}
}
}
複製程式碼
如果開啟了HA,則_當且僅當當前namenode處於active狀態,且safemode關閉的狀態下,FsNamesystem#isPopulatingReplQueues()才會返回true_;否則返回false。
根據前文對NameNode.<init>()
方法的分析,如果未開啟HA,namenode將被標記為active狀態;啟動過程中,將開啟safemode。那麼,何時才會關閉safemode呢?等到彙報的資料塊的比例超過設定的閾值,就會關閉safemode,標誌著namenode啟動過程的完成。
現在啟動一個datenode(猴子之前向叢集上傳過幾個檔案),等待第一次資料塊彙報完成後,繼續來分析BlockManager#computeDatanodeWork()與BlockManager#processPendingReplications()。
BlockManager#computeDatanodeWork()
BlockManager#computeDatanodeWork():
int computeDatanodeWork() {
if (namesystem.isInSafeMode()) {
return 0;
}
// 流控相關
final int numlive = heartbeatManager.getLiveDatanodeCount();
final int blocksToProcess = numlive
* this.blocksReplWorkMultiplier;
final int nodesToProcess = (int) Math.ceil(numlive
* this.blocksInvalidateWorkPct);
// 計算可進行副本複製的任務,最後返回任務數
int workFound = this.computeReplicationWork(blocksToProcess);
// 更新狀態等
namesystem.writeLock();
try {
this.updateState();
this.scheduledReplicationBlocksCount = workFound;
} finally {
namesystem.writeUnlock();
}
// 計算可進行副本刪除的任務,最後返回任務數
workFound += this.computeInvalidateWork(nodesToProcess);
// 返回總任務數
return workFound;
}
複製程式碼
BlockManager#computeReplicationWork()遍歷需要複製資料塊緩衝區BlockManager#neededReplications
,計算可進行副本複製的任務,放入正在複製資料塊緩衝區BlockManager#pendingReplications
,最後返回任務數。
BlockManager#computeInvalidateWork()遍歷需要刪除資料塊緩衝區BlockManager#excessReplicateMap
,計算可進行副本刪除的任務,放入正在刪除資料塊緩衝區BlockManager#invalidateBlocks
,最後返回任務數。
namenode對副本複製和副本刪除做了一些簡單的“流控”(將工作理解為網路流量,對工作數的控制):
- 對副本複製:限制每批次進行副本複製的資料塊總數,最多為叢集存活datanode總數的
${dfs.namenode.replication.work.multiplier.per.iteration}
倍,預設為2。 - 對副本刪除:限制每批次進行副本刪除涉及的datanode總數,最多為叢集存活datanode總數的
${dfs.namenode.invalidate.work.pct.per.iteration}
倍,預設為32%;限制每批次涉及的每個datanode上刪除的副本總數,最多為${dfs.block.invalidate.limit}
,預設為1000。
例如叢集有1000個存活節點,使用預設引數,則每批次最多建立1000 * 2 = 2000
個資料塊的副本複製工作,最多建立1000 * 32% * 1000 = 32w
個副本。
可以看到,副本複製的任務數上限遠大於副本刪除。這是因為,副本複製需要在datanode之間複製資料塊,佔用大量網路資源,如果不限制同時進行的副本複製任務數,很容易造成網路擁塞,影響整個叢集的效能;而副本刪除只涉及datanode內部的操作,甚至,對於大部分作業系統而言,檔案remove操作只需要操作類似檔案分配表(File Allocation table,FAT)的結構,成本非常小。
BlockManager#processPendingReplications()
BlockManager#processPendingReplications():
private void processPendingReplications() {
// 獲取全部的複製超時資料塊,並清空複製超時資料塊緩衝區
Block[] timedOutItems = pendingReplications.getTimedOutBlocks();
if (timedOutItems != null) {
namesystem.writeLock();
try {
for (int i = 0; i < timedOutItems.length; i++) {
// 計算各狀態的副本數
NumberReplicas num = countNodes(timedOutItems[i]);
// 如果過期的待複製資料塊仍然需要被複制,則將其新增回需要複製資料塊緩衝區
if (isNeededReplication(timedOutItems[i], getReplication(timedOutItems[i]),
num.liveReplicas())) {
neededReplications.add(timedOutItems[i],
num.liveReplicas(),
num.decommissionedReplicas(),
getReplication(timedOutItems[i]));
}
}
} finally {
namesystem.writeUnlock();
}
}
}
...
private boolean isNeededReplication(Block b, int expected, int current) {
// 如果當前的存活副本數小於副本系數,或資料塊沒有足夠的機架分佈,就需要繼續複製
return current < expected || !blockHasEnoughRacks(b);
}
複製程式碼
BlockManager#processPendingReplications()每次都_從複製超時資料塊緩衝區BlockManager#pendingReplications#timedOutItems
中取出全部的複製超時資料塊,如果這些資料塊還需要被複制,則將其重新加入需要複製資料塊緩衝區BlockManager#neededReplications
_。等待BlockManager#ReplicationMonitor執行緒在下一批次計算這些副本複製任務。
至此,namenode啟動流程的原始碼已經走完。
總結
datanode的主要責任是資料塊(檔案內容)的讀寫,因此,datanode的啟動流程主要關注的是與客戶端、namenode通訊的工作執行緒,底層的儲存管理機制等。
而namenode的主要責任是檔案元資訊與資料塊對映的管理。相應的,namenode的啟動流程需要關注與客戶端、datanode通訊的工作執行緒,檔案元資訊的管理機制,資料塊的管理機制等。其中,RpcServer主要負責與客戶端、datanode通訊,FSDirectory主要負責管理檔案元資訊,二者中的難點分別為RPC機制和名稱空間備份機制,本文簡單提及,沒有深入。重點筆墨放在了namenode對資料塊的管理上,即namenode上資料塊的關鍵狀態轉換:
上圖使用dot語言 + graphiz + sublime text 3 + graphiz-prefiew製作,簡潔靈活逼格高,從此愛上狀態機。
解釋一下上圖:
- 開始狀態為
complete
(因為我們還沒有分析namenode上的寫資料塊流程),結束狀態為complete
或none
。 none
表示刪除後的狀態。- 除
none狀態
外,各狀態對應著總覽中的各緩衝區。
紅線組成了副本複製的關鍵流程;藍線組成了副本刪除的關鍵流程。與副本複製流程相比,副本刪除流程不需要區分timeout狀態
的資料塊(類似複製超時資料塊緩衝區BlockManager#pendingReplications#timedOutItems
),更不需要區分刪除失敗等狀態。這是因為,副本刪除命令被髮出後,namenode即認為副本刪除成功;如果實際上刪除失敗(超時等原因),datanode必然會再次彙報目標資料塊,namenode發現已經有足夠的存活副本,則將目標資料塊再次加入需要刪除資料塊緩衝區BlockManager#excessReplicateMap
,即資料塊再次轉為excess_replicate狀態
。
此處並不是完整的
NNBlock狀態機
(仿照Yarn中RMApp的命名),隨著以後的分析,還要引入“資料塊正在寫”、“資料塊損壞”等狀態。
吐槽
最近狀態不好,近一週看不下去原始碼。看原始碼太缺少成就感,時間長了實在熬人,,,有沒有建議?
本文連結:原始碼|HDFS之NameNode:啟動過程
作者:猴子007
出處:monkeysayhi.github.io
本文基於 知識共享署名-相同方式共享 4.0 國際許可協議釋出,歡迎轉載,演繹或用於商業目的,但是必須保留本文的署名及連結。