Overview
The startup of a Hadoop NameNode is mainly driven by NameNode.java. At the code level, the entry point is the main method, which delegates the real work to createNameNode.
The essential part of main is quite small:
NameNode namenode = createNameNode(argv, null);
if (namenode != null) {
namenode.join();
}
From this point on, the startup logic is centered around createNameNode. Its work can be understood in two major stages.
The first stage is argument parsing. The most important parsed value is startOpt, because it determines what the current invocation of the NameNode should do. It may start normally, format metadata, perform rollback, import a checkpoint, initialize shared edits, and so on. The supported startup options include the following:
FORMAT ("-format"),
CLUSTERID ("-clusterid"),
GENCLUSTERID ("-genclusterid"),
REGULAR ("-regular"),
BACKUP ("-backup"),
CHECKPOINT("-checkpoint"),
UPGRADE ("-upgrade"),
ROLLBACK("-rollback"),
ROLLINGUPGRADE("-rollingUpgrade"),
IMPORT ("-importCheckpoint"),
BOOTSTRAPSTANDBY("-bootstrapStandby"),
INITIALIZESHAREDEDITS("-initializeSharedEdits"),
RECOVER ("-recover"),
FORCE("-force"),
NONINTERACTIVE("-nonInteractive"),
SKIPSHAREDEDITSCHECK("-skipSharedEditsCheck"),
RENAMERESERVED("-renameReserved"),
METADATAVERSION("-metadataVersion"),
UPGRADEONLY("-upgradeOnly"),
HOTSWAP("-hotswap"),
OBSERVER("-observer");
Under normal circumstances, the process proceeds into the regular NameNode startup path. The second stage is then either starting the NameNode itself or executing one of the special operations, such as format.
Entering the NameNode startup path
The core startup logic is located in the NameNode constructor. During construction, the NameNode first determines whether HA is enabled, creates the corresponding HA state, initializes common keys, and then invokes initialize to bring up the main components.
this.haEnabled = HAUtil.isHAEnabled(conf, nsId);
// 检查HA的状态,主要是判断当前启动的是主实例还是备实例
state = createHAState(getStartupOption(conf));
this.allowStaleStandbyReads = HAUtil.shouldAllowStandbyReads(conf);
this.haContext = createHAContext();
try {
initializeGenericKeys(conf, nsId, namenodeId);
// 启动NameNode
initialize(getConf());
state.prepareToEnterState(haContext);
try {
haContext.writeLock();
state.enterState(haContext);
} finally {
haContext.writeUnlock();
}
} catch (IOException e) {
this.stopAtException(e);
throw e;
} catch (HadoopIllegalArgumentException e) {
this.stopAtException(e);
throw e;
}
Here, HAUtil.isHAEnabled(conf, nsId) checks whether the current namespace is configured for high availability. Then createHAState(getStartupOption(conf)) determines the initial HA role of this NameNode instance, mainly distinguishing whether it should enter an active-like role or a standby-like role according to the startup option.
If initialization fails with either IOException or HadoopIllegalArgumentException, the NameNode calls stopAtException(e) before rethrowing the exception.
What initialize does
The initialize method wires together several fundamental NameNode services. It performs security login, initializes metrics, starts the JVM pause monitor, starts the HTTP server when appropriate, loads filesystem metadata, creates the RPC server, and finally starts the common services.
protected void initialize(Configuration conf) throws IOException {
// .... 省略
//登录kerberos
UserGroupInformation.setConfiguration(conf);
loginAsNameNodeUser(conf);
// 初始化监控信息
NameNode.initMetrics(conf, this.getRole());
StartupProgressMetrics.register(startupProgress);
pauseMonitor = new JvmPauseMonitor();
pauseMonitor.init(conf);
pauseMonitor.start();
metrics.getJvmMetrics().setPauseMonitor(pauseMonitor);
// .... 省略
if (NamenodeRole.NAMENODE == role) {
startHttpServer(conf);
}
// 从本地加载FSImage,并且与Editlog合并产生新的FSImage
loadNamesystem(conf);
//TODO 待确认用途
startAliasMapServerIfNecessary(conf);
//创建rpcserver,封装了NameNodeRpcServer、ClientRPCServer
//支持ClientNameNodeProtocol、DataNodeProtocolPB等协议
rpcServer = createRpcServer(conf);
initReconfigurableBackoffKey();
// .... 省略
if (NamenodeRole.NAMENODE == role) {
httpServer.setNameNodeAddress(getNameNodeAddress());
httpServer.setFSImage(getFSImage());
if (levelDBAliasMapServer != null) {
httpServer.setAliasMap(levelDBAliasMapServer.getAliasMap());
}
}
//启动执行多个重要的工作线程
startCommonServices(conf);
startMetricsLogger(conf);
}
A few points are worth highlighting.
First, Kerberos-related configuration is prepared through UserGroupInformation.setConfiguration(conf), followed by loginAsNameNodeUser(conf). This ensures the NameNode runs under the expected security identity when security is enabled.
Second, monitoring is initialized early. NameNode.initMetrics sets up NameNode metrics, StartupProgressMetrics.register(startupProgress) exposes startup progress, and JvmPauseMonitor is started to track JVM pauses.
Third, for the regular NAMENODE role, the HTTP server is started before the namespace is loaded. After loadNamesystem(conf) completes, the HTTP server is given the NameNode address and the loaded FSImage.
The loadNamesystem(conf) step is especially important: it loads the local FSImage and merges it with the edit logs, producing the latest filesystem namespace state.
After that, createRpcServer(conf) creates the RPC server. This wraps components such as NameNodeRpcServer and ClientRPCServer, and supports protocols including ClientNameNodeProtocol and DataNodeProtocolPB.
Finally, startCommonServices(conf) starts a set of important background services and worker threads, followed by startMetricsLogger(conf).
Starting common NameNode services
The startCommonServices method starts the core services required by the NameNode after the namespace and RPC layer have been prepared.
private void startCommonServices(Configuration conf) throws IOException {
// 创建NameNodeResourceChecker、激活BlockManager等
namesystem.startCommonServices(conf, haContext);
registerNNSMXBean();
if (NamenodeRole.NAMENODE != role) {
startHttpServer(conf);
httpServer.setNameNodeAddress(getNameNodeAddress());
httpServer.setFSImage(getFSImage());
if (levelDBAliasMapServer != null) {
httpServer.setAliasMap(levelDBAliasMapServer.getAliasMap());
}
}
// 启动rpc服务
rpcServer.start();
try {
// 获取启动插件列表
plugins = conf.getInstances(DFS_NAMENODE_PLUGINS_KEY,
ServicePlugin.class);
} catch (RuntimeException e) {
String pluginsValue = conf.get(DFS_NAMENODE_PLUGINS_KEY);
LOG.error("Unable to load NameNode plugins. Specified list of plugins: " +
pluginsValue, e);
throw e;
}
// 启动所有插件
for (ServicePlugin p: plugins) {
try {
// 调用插件的start接口,需要插件自己实现,需要实现接口ServicePlugin
p.start(this);
} catch (Throwable t) {
LOG.warn("ServicePlugin " + p + " could not be started", t);
}
}
LOG.info(getRole() + " RPC up at: " + getNameNodeAddress());
if (rpcServer.getServiceRpcAddress() != null) {
LOG.info(getRole() + " service RPC up at: "
+ rpcServer.getServiceRpcAddress());
}
}
The first call, namesystem.startCommonServices(conf, haContext), is where the FSNamesystem starts its own internal services. This includes creating the NameNodeResourceChecker and activating the BlockManager.
The method then registers the NameNode MXBean. If the role is not NamenodeRole.NAMENODE, the HTTP server is started at this stage instead, and the NameNode address, FSImage, and optional alias map are attached to it.
Next, the RPC service is started through rpcServer.start().
Plugin startup happens after the RPC server is up. The plugin instances are loaded from the configuration key DFS_NAMENODE_PLUGINS_KEY, and each plugin is expected to implement ServicePlugin. For each plugin, the NameNode invokes p.start(this). A plugin startup failure is logged as a warning, but the loop continues for the remaining plugins.
At the end, the NameNode logs its RPC address. If a service RPC address is configured, that address is logged as well.
Inside namesystem.startCommonServices
The FSNamesystem version of startCommonServices is mainly responsible for starting the resource checker and the block management subsystem.
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 !blockManager.isPopulatingReplQueues();
StartupProgress prog = NameNode.getStartupProgress();
prog.beginPhase(Phase.SAFEMODE);
//获取已完成的数据块总量
long completeBlocksTotal = getCompleteBlocksTotal();
prog.setTotal(Phase.SAFEMODE, STEP_AWAITING_REPORTED_BLOCKS,
completeBlocksTotal);
// 激活blockManager,blockManager负责管理文件系统中文件的物理块与实际存储位置的映射关系,
// 是NameNode的核心功能之一。
blockManager.activate(conf, completeBlocksTotal);
} finally {
writeUnlock("startCommonServices");
}
registerMXBean();
DefaultMetricsSystem.instance().register(this);
if (inodeAttributeProvider != null) {
inodeAttributeProvider.start();
dir.setINodeAttributeProvider(inodeAttributeProvider);
}
// 注册快照管理器
snapshotManager.registerMXBean();
InetSocketAddress serviceAddress = NameNode.getServiceAddress(conf, true);
this.nameNodeHostName = (serviceAddress != null) ? serviceAddress.getHostName() : "";
}
The method begins by registering an MBean for FSNamesystemState, then takes the write lock and stores the HA context.
Inside the locked section, it creates NameNodeResourceChecker and immediately calls checkAvailableResources(). This is used to verify that the NameNode has the required local resources available before proceeding.
It then enters the startup progress phase for safe mode:
prog.beginPhase(Phase.SAFEMODE)marks the beginning of the safe mode startup phase.getCompleteBlocksTotal()obtains the total number of complete blocks.prog.setTotal(Phase.SAFEMODE, STEP_AWAITING_REPORTED_BLOCKS, completeBlocksTotal)records the amount of work expected while waiting for block reports.
The most important operation in this method is blockManager.activate(conf, completeBlocksTotal). The BlockManager maintains the mapping between filesystem blocks and their actual storage locations on DataNodes. This makes it one of the central components of the NameNode.
After the write lock is released, the method registers additional MXBeans and metrics. If an inodeAttributeProvider exists, it is started and attached to the directory manager. The snapshot manager also registers its MXBean. Finally, the service address is read from configuration, and the host name is stored in nameNodeHostName.
Activating the BlockManager
The BlockManager activation step mainly initializes and starts the block management related services. The key parts include:
pendingReconstructiondatanodeManagerbmSafeMode
The implementation is as follows:
public void activate(Configuration conf, long blockTotal) {
pendingReconstruction.start();
// 初始化datanodeManager
datanodeManager.activate(conf);
this.redundancyThread.setName("RedundancyMonitor");
this.redundancyThread.start();
this.markedDeleteBlockScrubberThread.setName("MarkedDeleteBlockScrubberThread");
this.markedDeleteBlockScrubberThread.start();
this.blockReportThread.start();
mxBeanName = MBeans.register("NameNode", "BlockStats", this);
bmSafeMode.activate(blockTotal);
}
The first component started is pendingReconstruction, which is related to blocks waiting for reconstruction. Then datanodeManager.activate(conf) initializes the DataNode manager.
Several background threads are also started:
RedundancyMonitor, responsible for redundancy-related monitoring work.MarkedDeleteBlockScrubberThread, used for processing blocks marked for deletion.blockReportThread, which participates in block report related processing.
The BlockManager then registers a BlockStats MBean under NameNode, and finally activates bmSafeMode with the total block count passed in from FSNamesystem.
From the overall startup sequence, the NameNode begins with command-line option parsing, enters the constructor-based initialization flow, loads namespace metadata through loadNamesystem, starts RPC and HTTP-facing services, and then activates the internal FSNamesystem and BlockManager services that make block tracking and safe mode progress possible.