小白也能看懂的源碼分析系列(1)—HADOOP的NameNode啓動過程
一、前言
HADOOP作爲大數據的基石,甚至是大數據的代名詞,各種耳熟能詳的框架基於HADOOP生態展開,發展日益迅速,HADOOP生態的完善,離不開HADOOP這個項目的偉大,作爲一名大數據方向的工程師或者研究人員,這是必須要熟悉的框架,想要進一步深入的理解它的偉大之處,外面必須要熟悉它的原理,原理從何而來?—源碼。我們這一節會以一個簡單的方式來分析hadoop的源碼,小白不要一看到源碼分析專題就頭疼避而遠之,這裏我保證你看得懂!
二、源碼正確打開方式
step1:
我們單純的分析NameNode源碼,沒必要把hadoop源碼包下載下來,如果下載全量源碼,對沒有fq條件的同學不友好,下載依賴會很麻煩,我們可以新建一個空的maven項目,把下面的依賴配到pom.xml文件裏面,再把對應的源碼下載下來
<!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-common -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.7.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-hdfs -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>2.7.3</version>
</dependency>
step2:
我們這次的目的是看NameNode源碼,使用idea的類查找快捷鍵Ctrl+N,再輸入NameNode既可查找到
step3:
我們點進去的只是idea反編譯後的文件,需要點擊右上角的下載源碼,這樣就能順利的查看了,我們在查看源碼的過程中,只要看到右上角有Download sources的提示,果斷點擊下載就行了
step4:
任何你在linux中通過jps命令看到的進程,NameNode、DataNode、NodeManger…一切看得到的,都是通過main方法啓動,所以,這是進入源碼的第一步,找到NameNode裏面的main方法,終於可以進入正題了
必須要知道的快捷鍵:
Ctrl + Alt + ← 表示返回上一步的位置;Ctrl + Alt + → 進入當前的下一步前提是你進入過下一步。不懂的話這兩個快捷鍵實操一下就知道什麼意思,分析源碼必須知道的這兩個快捷鍵!
三、分析NameNode啓動過程
進入main方法後,我們可以看到邏輯很簡單,小白也能看的懂,顯示解析傳入的參數,打印啓動的信息,最關鍵的一步是通過createNameNode(argv, null)
方法新建NameNode
對象
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);
if (namenode != null) {
namenode.join();
}
} catch (Throwable e) {
LOG.error("Failed to start namenode.", e);
terminate(1, e);
}
}
我們再進入createNameNode(argv, null)
方法,重點在switch裏面,switch前面是一堆解析參數的邏輯,switch裏面會根據傳入的參數執行不同的邏輯,關於啓動參數,switch裏面第一個選項,我們最熟悉的也就是-format
,也就是我們搭建集羣的時候會執行的hadoop namenode –format命令。默認不加參數的,也就是啓動NameNode,在最後的default
邏輯裏面,會new NameNode()
對象
public static NameNode createNameNode(String argv[], Configuration conf)
throws IOException {
LOG.info("createNameNode " + Arrays.asList(argv));
if (conf == null)
conf = new HdfsConfiguration();
// 解析一般的參數,也就是我們熟悉的key-value的形式,會set到我們常用的Configuration.
GenericOptionsParser hParser = new GenericOptionsParser(conf, argv);
argv = hParser.getRemainingArgs();
// 解析其它特殊的啓動參數,在下面的switch中
StartupOption startOpt = parseArguments(argv);
if (startOpt == null) {
printUsage(System.err);
return null;
}
setStartupOption(conf, startOpt);
// 判斷 startOpt 啓動參數
switch (startOpt) {
// 我們最熟悉的format參數,格式化NameNode,等同於命令行hadoop namenode –format
case FORMAT: {
boolean aborted = format(conf, startOpt.getForceFormat(),
startOpt.getInteractiveFormat());
terminate(aborted ? 1 : 0);
return null; // avoid javac warning
}
case GENCLUSTERID: {
System.err.println("Generating new cluster id:");
System.out.println(NNStorage.newClusterID());
terminate(0);
return null;
}
case FINALIZE: {
System.err.println("Use of the argument '" + StartupOption.FINALIZE +
"' is no longer supported. To finalize an upgrade, start the NN " +
" and then run `hdfs dfsadmin -finalizeUpgrade'");
terminate(1);
return null; // avoid javac warning
}
case ROLLBACK: {
boolean aborted = doRollback(conf, true);
terminate(aborted ? 1 : 0);
return null; // avoid warning
}
case BOOTSTRAPSTANDBY: {
String toolArgs[] = Arrays.copyOfRange(argv, 1, argv.length);
int rc = BootstrapStandby.run(toolArgs, conf);
terminate(rc);
return null; // avoid warning
}
case INITIALIZESHAREDEDITS: {
boolean aborted = initializeSharedEdits(conf,
startOpt.getForceFormat(),
startOpt.getInteractiveFormat());
terminate(aborted ? 1 : 0);
return null; // avoid warning
}
case BACKUP:
case CHECKPOINT: {
NamenodeRole role = startOpt.toNodeRole();
DefaultMetricsSystem.initialize(role.toString().replace(" ", ""));
return new BackupNode(conf, role);
}
case RECOVER: {
NameNode.doRecovery(startOpt, conf);
return null;
}
case METADATAVERSION: {
printMetadataVersion(conf);
terminate(0);
return null; // avoid javac warning
}
case UPGRADEONLY: {
DefaultMetricsSystem.initialize("NameNode");
new NameNode(conf);
terminate(0);
return null;
}
default: {
// 默認進入啓動NameNode的邏輯
DefaultMetricsSystem.initialize("NameNode");
return new NameNode(conf);
}
}
}
進入new NameNode(conf)
我一路點進去,進入構造方法,重點方法在initialize(conf)
:
protected NameNode(Configuration conf, NamenodeRole role)
throws IOException {
this.conf = conf;
this.role = role;
setClientNamenodeAddress(conf);
String nsId = getNameServiceId(conf);
String namenodeId = HAUtil.getNameNodeId(conf, nsId);
// 從配置裏面獲取是否開啓了HA機制
this.haEnabled = HAUtil.isHAEnabled(conf, nsId);
// 獲取當前namenode的狀態,總共有3中實現,ActiveState、StandbyState、BackupState
state = createHAState(getStartupOption(conf));
this.allowStaleStandbyReads = HAUtil.shouldAllowStandbyReads(conf);
this.haContext = createHAContext();
try {
initializeGenericKeys(conf, nsId, namenodeId);
// 初始化方法,主要邏輯都在這裏面,重點看這裏
initialize(conf);
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;
}
this.started.set(true);
}
重點看initialize(conf)
方法,進入此方法,重點邏輯方法:
loadNamesystem(conf)
:- 執行
FSNamesystem
的初始化邏輯,加載fsimage文件進內存 - 初始化
FSNamesystem
時,會實例化BlockManager
,BlockManager
保存了Datanode
塊的信息
- 執行
startCommonServices(conf)
- 啓動rpc server,包括clientRpcServer和serviceRpcServer,NameNode要跟其它服務進行rpc通信遠程調用其它服務的方法,所以是需要clientRpcServer的,這點不懂rpc的通信可以仔細瞭解下rpc的原理
- 啓用
blockManager
,主要功能是兩個,第一是對block進行管理上報,第二是啓動datanodeManager
,負責註冊到NameNode,管理了NameNode
與DataNode
的心跳連接線程與上下線
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);
// 如果使用kerberos做認證的話,這裏要使用配置的用戶登錄
loginAsNameNodeUser(conf);
// 這裏是初始化一些Metrics,做監控分析使用
NameNode.initMetrics(conf, this.getRole());
StartupProgressMetrics.register(startupProgress);
if (NamenodeRole.NAMENODE == role) {
startHttpServer(conf);
}
this.spanReceiverHost =
SpanReceiverHost.get(conf, DFSConfigKeys.DFS_SERVER_HTRACE_PREFIX);
// 這裏是很關鍵的一步,會從本地加載fsimage文件進內存,fsimage是hdfs核心的一部分,存放了hdfs全量的文件信息,我們平常看到的所有hdfs目錄信息,都在這個文件裏面記錄並持久化
loadNamesystem(conf);
// 啓動NameNode的rpcserver,用於與其它rpc客戶端進行交互做準備,大家都知道hadoop之前是通過rpc進行通信,這裏是關鍵的一步,裏面初始化並添加了一堆的Protocol協議
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());
}
pauseMonitor = new JvmPauseMonitor(conf);
pauseMonitor.start();
metrics.getJvmMetrics().setPauseMonitor(pauseMonitor);
// 這裏是很關鍵的第二步,會啓動rpc server,啓動blockManager
startCommonServices(conf);
}
到這裏,我們就不再繼續深入,裏面的邏輯相對複雜,可能需要花長篇大論來描述,讀者也可能不會有足夠的耐心讀下去,知道NameNode啓動的流程中大致做了哪些重要的事就行了,重點描述都在上面,有興趣的話可以繼續追蹤源碼,閱讀過程中沒必要每一行都懂,找到重點方法步入即可。想進一步深入瞭解,建議看完loadNamesystem(conf)
和startCommonServices(conf)
,會對NameNode
啓動過程會有更深入的瞭解。
四、總結
本文寫的有點淺顯,主要目的是開個頭,帶讀者入個門,讓讀者知道,哦,原來Hadoop的源碼也就那麼回事,都看的懂。本教程是面向對java基礎、大數據基礎相對不那麼深入的童鞋,小白也能看懂的源碼分析系列我打算一直寫下去,保證人人都能看懂的情況下講解源碼分析過程。想要了解技術的本質,光會用是不行的,必須深入源碼分析框架的基礎原理,再高的房子都是由磚塊堆積的,房子的形狀千變萬化,萬變不離其宗的是蓋樓的轉頭!