本文将结合hadoop2.7.0版本的源码与UML图对NameNode的启动流程进行深入剖析,旨在更深入地理解NameNode启动的整体逻辑
第一、二步:找到NameNode的启动入口main()方法,进入方法体createNameNode()
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);
//TODO:创建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()方法中会对传入的参数进行解析,并判断事件类型,由于本文研究的是启动流程,故可直接看default分支
default: {
DefaultMetricsSystem.initialize("NameNode");
/*TODO:以上都不是的,就是实例化NameNode*/
return new NameNode(conf);
}
第四步:在NameNode的构造方法中调用了initialize初始化方法
protected NameNode(Configuration conf, NamenodeRole role)
throws IOException {
...
try {
initializeGenericKeys(conf, nsId, namenodeId);
//TODO: 初始化方法
initialize(conf);
...
}
第五步:调用startHttpServer(),启动HttpServer
第六步:进入startHttpServer()方法,并实例化NameNodeHttpServer类
private void startHttpServer(final Configuration conf) throws IOException {
//TODO: 设置httpServer的ip、端口
httpServer = new NameNodeHttpServer(conf, this, getHttpServerBindAddress(conf));
httpServer.start();
httpServer.setStartupProgress(startupProgress);
}
第七步:调用NameNodeHttpServer类的start()方法
第八、九步:在start()方法内创建了HttpServer2,并随后启动了httpServer,即hadoop的50070端口监控页面
void start() throws IOException {
...
//TODO:启动服务端
HttpServer2.Builder builder = DFSUtil.httpServerTemplateForNNAndJN(conf,
httpAddr, httpsAddr, "hdfs",
DFSConfigKeys.DFS_NAMENODE_KERBEROS_INTERNAL_SPNEGO_PRINCIPAL_KEY,
DFSConfigKeys.DFS_NAMENODE_KEYTAB_FILE_KEY);
httpServer = builder.build();
if (policy.isHttpsEnabled()) {
// assume same ssl port for all datanodes
InetSocketAddress datanodeSslPort = NetUtils.createSocketAddr(conf.getTrimmed(
DFSConfigKeys.DFS_DATANODE_HTTPS_ADDRESS_KEY, infoHost + ":"
+ DFSConfigKeys.DFS_DATANODE_HTTPS_DEFAULT_PORT));
httpServer.setAttribute(DFSConfigKeys.DFS_DATANODE_HTTPS_PORT_KEY,
datanodeSslPort.getPort());
}
initWebHdfs(conf);
httpServer.setAttribute(NAMENODE_ATTRIBUTE_KEY, nn);
httpServer.setAttribute(JspHelper.CURRENT_CONF, conf);
// TODO:往服务端绑定了servlet
setupServlets(httpServer, conf);
// TODO:启动HttpServer服务,对外开放50070端口
httpServer.start();
...
}
第十步:回到NameNode的initialize()方法,继续调用createRpcServer()创建Hadoop RPC
第十一步:实例化NameNodeRpcServer类
protected NameNodeRpcServer createRpcServer(Configuration conf)
throws IOException {
return new NameNodeRpcServer(conf, this);
}
第十二步:在NameNodeRpcServer的构造方法中创建serviceRpcServer,主要用于维护NameNode节点与DataNode节点之间的RPC调用,如心跳机制等
public NameNodeRpcServer(Configuration conf, NameNode nn)
throws IOException {
...
//TODO:启动ServiceRpcServer
this.serviceRpcServer = new RPC.Builder(conf)
.setProtocol(
org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolPB.class)
.setInstance(clientNNPbService)
.setBindAddress(bindHost)
.setPort(serviceRpcAddr.getPort()).setNumHandlers(serviceHandlerCount)
.setVerbose(false)
.setSecretManager(namesystem.getDelegationTokenSecretManager())
.build();
...
}
第十三步:在NameNodeRpcServer的构造方法中创建clientRpcServer,主要用于客户端与集群之间的操作
public NameNodeRpcServer(Configuration conf, NameNode nn)
throws IOException {
...
//TODO: 启动clientRpcServer
this.clientRpcServer = new RPC.Builder(conf)
.setProtocol(
org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolPB.class)
.setInstance(clientNNPbService).setBindAddress(bindHost)
.setPort(rpcAddr.getPort()).setNumHandlers(handlerCount)
.setVerbose(false)
.setSecretManager(namesystem.getDelegationTokenSecretManager()).build();
...
}
第十四步:再次回到NameNode的initialize()方法,调用startCommonServices(),该方法主要的功能有两点:1、检查是否有足够的磁盘空间存储元数据;2、进入安全模式检查,检查是否可以退出安全模式
// TODO 启动一些公共服务,NameNode PRC的服务就是在里面启动的
// 1)进行资源检查,检查是否有足够磁盘存储元数据
// 2)进入安全模式检查,检查是否可以退出安全模式
startCommonServices(conf);
第十五步:调用FSNameSystem的startCommonServices()方法
private void startCommonServices(Configuration conf) throws IOException {
//TODO:FSNameSystem是管理HDFS的元数据
namesystem.startCommonServices(conf, haContext);
registerNNSMXBean();
if (NamenodeRole.NAMENODE != role) {
startHttpServer(conf);
httpServer.setNameNodeAddress(getNameNodeAddress());
httpServer.setFSImage(getFSImage());
}
//启动RPC
rpcServer.start();
...
}
第十六步:进入FSNameSystem的startCommonServices()方法,首先执行了checkAvailableResources()方法,检查是否有足够的磁盘空间存储元数据信息
void startCommonServices(Configuration conf, HAContext haContext) throws IOException {
this.registerMBean(); // register the MBean for the FSNamesystemState
writeLock();
this.haContext = haContext;
try {
// 需要检查三个目录,因为三个目录都涉及到了元数据
// NameNode的两个目录:存储fsimage的目录、存储editlog的目录
// JournalNode也存储有元数据,HA
nnResourceChecker = new NameNodeResourceChecker(conf);
// TODO:检查是否有足够的磁盘存储元数据
checkAvailableResources();
assert safeMode != null && !isPopulatingReplQueues();
StartupProgress prog = NameNode.getStartupProgress();
prog.beginPhase(Phase.SAFEMODE);
prog.setTotal(Phase.SAFEMODE, STEP_AWAITING_REPORTED_BLOCKS,
getCompleteBlocksTotal());
// TODO:HDFS的安全模式的检查(比较重要)
setBlockTotal();
// TODO:启动重要服务
blockManager.activate(conf);
} finally {
writeUnlock();
}
...
}
void checkAvailableResources() {
Preconditions.checkState(nnResourceChecker != null,
"nnResourceChecker not initialized");
// TODO:返回资源是否充足
hasResourcesAvailable = nnResourceChecker.hasAvailableDiskSpace();
}
第十七步:调用setBlockTotal()进行安全模式的检查
public void setBlockTotal() {
// safeMode is volatile, and may be set to null at any time
SafeModeInfo safeMode = this.safeMode;
if (safeMode == null)
return;
// TODO:设置安全模式
// getCompleteBlocksTotal()获取集群正常的block个数
safeMode.setBlockTotal((int)getCompleteBlocksTotal());
}
第十八步:调用SafeModeInfo实例的setBlockTotal()设置安全模式
private synchronized void setBlockTotal(int total) {
this.blockTotal = total;
// TODO:计算阈值
// 举例:1000 * 0.999 = 999 threshold默认是0.999
this.blockThreshold = (int) (blockTotal * threshold);
this.blockReplQueueThreshold =
(int) (blockTotal * replQueueThreshold);
if (haEnabled) {
// After we initialize the block count, any further namespace
// modifications done while in safe mode need to keep track
// of the number of total blocks in the system.
this.shouldIncrementallyTrackBlocks = true;
}
if(blockSafe < 0)
this.blockSafe = 0;
// TODO:检查安全模式
checkMode();
}
第十九步:调用checkMode()检查安全模式
private void checkMode() {
...
// if smmthread is already running, the block threshold must have been
// reached before, there is no need to enter the safe mode again
// TODO:判断是否进入安全模式
if (smmthread == null && needEnter()) {
// TODO:进入安全模式
enter();
// check if we are ready to initialize replication queues
if (canInitializeReplQueues() && !isPopulatingReplQueues()
&& !haEnabled) {
initializeReplQueues();
}
reportStatus("STATE* Safe mode ON.", false);
return;
}
...
}
第二十步:调用needEnter()方法判断是需要进入安全模式
// TODO:是否需要进入安全模式
private boolean needEnter() {
// 1)处于安全的block个数是否小于阈值
return (threshold != 0 && blockSafe < blockThreshold) ||
// 2)存活的dataNode个数是否小于阈值
(datanodeThreshold != 0 && getNumLiveDataNodes() < datanodeThreshold) ||
// 3)NameNode存储元数据的是否有足够的磁盘空间,默认剩余空间需要大于100M
(!nameNodeHasResourcesAvailable());
}
第二十一步:回到FSNamesystem类的startCommonServices方法中,调用BlockManager的activate()方法启动服务
void startCommonServices(Configuration conf, HAContext haContext) throws IOException {
...
//TODO:启动重要服务
blockManager.activate(conf);
...
}
第二十二步:回到NameNode的startCommonServices()方法,调用NameNodeRpcServer的start(),完成NameNode的启动
private void startCommonServices(Configuration conf) throws IOException {
// TODO FSNameSystem是管理HDFS的元数据
namesystem.startCommonServices(conf, haContext);
registerNNSMXBean();
if (NamenodeRole.NAMENODE != role) {
startHttpServer(conf);
httpServer.setNameNodeAddress(getNameNodeAddress());
httpServer.setFSImage(getFSImage());
}
// 启动RPC
rpcServer.start();
...
}