ps: 本文参考hadoop-3.3.0
1 NameNode简介
namenode是hadoop架构中最重要的角色之一,NameNode主要管理管理着名称空间表(文件名与block的映射,存储在磁盘上且十分重要)和inodes表(块与机器的映射,只要namenode出现就会存在),通常情况下一个集群中有且仅有一个活跃的NameNode,从Hadoop2开始,为了保证集群中的高可用,可以配置两个namenode,一个active另一个为standby。但这种情况下,当集群规模达到一定程度时,namenode仍然会成为集群的瓶颈,因此在这种情况下,hadoop Federation应运而生,即一个集群中允许多组NameNode提供服务,每组namenode各自分管一部分目录(NameSpace,BlockPool),彼此之间相互隔离,但共享相同的DataNode。
但Federation模式虽然间接的扩展了NameNode,但是由于Federation中schema需要使用ViewFs且无法兼容HDFS,因此这种情况下需要对运行在集群上的schema做一个替换。因此Hadoop3中新增的的多个NameNode的特性就显得十分有效,理论上支持使用>2个NameNode,但官方建议使用3-5个NameNode节点。
还有一点内容需要了解,从hadoop3.3.0开始,社区新增了一个新feature,即新增一个observe namenode,为了解决active 访问负载过高的问题,将部分read request 转移到observer上可以大幅的降低active namenode的负载。
2 NameNode的启动
2.1 main()方法
首先进入NameNode的main方法,通过解析各个参数执行NameNode的启动:
public static void main(String argv[]) throws Exception {
// 这里会做一个判断是否是help操作
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);
// 进程持续运行,运行NameNodeRpcServer#join,从而实现rpc的通信
if (namenode != null) {
namenode.join();
}
} catch (Throwable e) {
LOG.error("Failed to start namenode.", e);
terminate(1, e);
}
}
2.2 createNameNode
此方法主要是用来创建一个NameNode,根据传进去的参数创建不同的NameNode,当没有特别的参数指定时,创建一个默认的NameNode,即正常的NameNode
public static NameNode createNameNode(String argv[], Configuration conf)
throws IOException {
LOG.info("createNameNode " + Arrays.asList(argv));
if (conf == null)
conf = new HdfsConfiguration();
// Parse out some generic args into Configuration.
GenericOptionsParser hParser = new GenericOptionsParser(conf, argv);
argv = hParser.getRemainingArgs();
// Parse the rest, NN specific args.
/**
* 获取启动方式:默认是regular
* 1. format:格式化,验证配置的目录(namenode dir,edit dir, share edit dir)
是否存在并且格式化是否是预期的
* 2. gen clusterId:生成新的cluster id
* 3. regular:正常启动namenode
* 4. backup:
* 5. checkpoint:和backup一样都是创建一个BackupNode,主要包括checkpoint和
backup的NameNode#role,用来周期性的将edit落盘,
backup还有一点不同的是会同步active的namespace信息
* 6. observer:3.3.0新增的一个namenode类型,可以减轻active namenode的读负载
* 7. upgrade或者upgrade only:升级操作
* 8. rollingUpgrade:滚动升级
* 9. rollback:从升级系统中回滚到前一个版本,这个参数必须在停止的集群分布式文件系统中使用
* 10. import:从指定的一个checkpoint中恢复集群
* 11. bootstrap standby:启动standby node,仅在初次HA的时候从active 复制namespace数据
* 12. initialize sharded edits:初始化share edit目录,便于standby nn启动
* 13. recover:单个元数据存储时可以通过此命令恢复namenode
* 14. metadata version:元数据信息
* 15. null:参数不正确
*/
StartupOption startOpt = parseArguments(argv);
if (startOpt == null) {
printUsage(System.err);
return null;
}
// 将startOpt添加到配置中,用户后面创建不同的NameNode
setStartupOption(conf, startOpt);
boolean aborted = false;
// 根据配置创建不同的NameNode
switch (startOpt) {
case FORMAT:
aborted = format(conf, startOpt.getForceFormat(),
startOpt.getInteractiveFormat());
terminate(aborted ? 1 : 0);
return null; // avoid javac warning
case GENCLUSTERID:
String clusterID = NNStorage.newClusterID();
LOG.info("Generated new cluster id: {}", clusterID);
terminate(0);
return null;
case ROLLBACK:
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:
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:
DefaultMetricsSystem.initialize("NameNode");
return new NameNode(conf);
}
}
这个方法主要是根据参数创建各种不同类型的NameNode,默认是Regular,创建普通的NameNode。另外还有其他几种方式:
- format:格式化NameNode,建立NameNode节点的文件结构。带有format参数启动NameNode节点时,首先启动NameNode节点,然后对其机型格式化,再关闭节点,如果文件目录已经存在当前文件系统,则会提示用户。它有两个参数nonInteractive和force,nonInteractive表示如果NameNode节点的文件夹在当前的底层文件系统中存在,那么用户将不会收到提示,并且当前的格式化会失败,force表示不管NameNode的目录存不存在,强制格式化NameNode节点,也不会提示用户,如果nonInteractive和force参数同时存在,那么force参数将会被忽略
- 其他几种方式参见注释
2.3 NameNode构造
protected NameNode(Configuration conf, NamenodeRole role)
throws IOException {
super(conf);
this.tracer = new Tracer.Builder("NameNode").
conf(TraceUtils.wrapHadoopConf(NAMENODE_HTRACE_PREFIX, conf)).
build();
this.tracerConfigurationManager =
new TracerConfigurationManager(NAMENODE_HTRACE_PREFIX, conf);
this.role = role;
String nsId = getNameServiceId(conf);
String namenodeId = HAUtil.getNameNodeId(conf, nsId);
clientNamenodeAddress = NameNodeUtils.getClientNamenodeAddress(
conf, nsId);
if (clientNamenodeAddress != null) {
LOG.info("Clients should use {} to access"
+ " this namenode/service.", clientNamenodeAddress);
}
// 是否启用HA
this.haEnabled = HAUtil.isHAEnabled(conf, nsId);
// 获取当前是Active或者Standby状态
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();
// 启动对应状态的服务,如active、standby
state.enterState(haContext);
} finally {
haContext.writeUnlock();
}
} catch (IOException e) {
this.stopAtException(e);
throw e;
} catch (HadoopIllegalArgumentException e) {
this.stopAtException(e);
throw e;
}
notBecomeActiveInSafemode = conf.getBoolean(
DFS_HA_NN_NOT_BECOME_ACTIVE_IN_SAFEMODE,
DFS_HA_NN_NOT_BECOME_ACTIVE_IN_SAFEMODE_DEFAULT);
this.started.set(true);
}
在初始化中会进行一下参数的补充,检查当前NameNode的状态,是否HA之类的,而后根据给定状态启动对应服务。最重要的是initialize(conf),用户namenode完成初始化操作。
2.4 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);
NameNode.initMetrics(conf, this.getRole());
StartupProgressMetrics.register(startupProgress);
pauseMonitor = new JvmPauseMonitor();
pauseMonitor.init(conf);
pauseMonitor.start();
metrics.getJvmMetrics().setPauseMonitor(pauseMonitor);
if (conf.getBoolean(DFS_NAMENODE_GC_TIME_MONITOR_ENABLE,
DFS_NAMENODE_GC_TIME_MONITOR_ENABLE_DEFAULT)) {
long observationWindow = conf.getTimeDuration(
DFS_NAMENODE_GC_TIME_MONITOR_OBSERVATION_WINDOW_MS,
DFS_NAMENODE_GC_TIME_MONITOR_OBSERVATION_WINDOW_MS_DEFAULT,
TimeUnit.MILLISECONDS);
long sleepInterval = conf.getTimeDuration(
DFS_NAMENODE_GC_TIME_MONITOR_SLEEP_INTERVAL_MS,
DFS_NAMENODE_GC_TIME_MONITOR_SLEEP_INTERVAL_MS_DEFAULT,
TimeUnit.MILLISECONDS);
gcTimeMonitor = new Builder().observationWindowMs(observationWindow)
.sleepIntervalMs(sleepInterval).build();
gcTimeMonitor.start();
metrics.getJvmMetrics().setGcTimeMonitor(gcTimeMonitor);
}
// 启动一个NameNodeHttpServer, 启动0.0.0.0:9870
if (NamenodeRole.NAMENODE == role) {
startHttpServer(conf);
}
// 根据配置中指定位置的edit和fsImage初始化FsNameSystem,加载fsImage
loadNamesystem(conf);
startAliasMapServerIfNecessary(conf);
// hadoop rpc
rpcServer = createRpcServer(conf);
initReconfigurableBackoffKey();
if (clientNamenodeAddress == null) {
// This is expected for MiniDFSCluster. Set it now using
// the RPC server's bind address.
clientNamenodeAddress =
NetUtils.getHostPortString(getNameNodeAddress());
LOG.info("Clients are to use " + clientNamenodeAddress + " to access"
+ " this namenode/service.");
}
if (NamenodeRole.NAMENODE == role) {
httpServer.setNameNodeAddress(getNameNodeAddress());
httpServer.setFSImage(getFSImage());
if (levelDBAliasMapServer != null) {
httpServer.setAliasMap(levelDBAliasMapServer.getAliasMap());
}
}
// 启用通用服务,在此之中会检查是否进入blockManager的安全模式
startCommonServices(conf);
startMetricsLogger(conf);
}
3 其他
在此主要介绍NameNode类中中比较重要的几个变量:
/**
* FSNamesystem 是瞬态和持久命名空间状态的容器,并在 NameNode 上完成所有记录工作。
* 主要作用如下:
* 1. 是 BlockManager、DatanodeManager、DelegationTokens、LeaseManager 等服务的容器。
* 2. 委托处理修改或检查命名空间的 RPC 调用
* 3. 任何只涉及块的东西(例如块报告),它都委托给 BlockManager
* 4. 任何只涉及文件信息(例如权限、mkdirs)的东西,它都会委托给 FSDirectory
* 5. 任何跨越上述两个组件的东西都应该在这里协调。
* 6. 记录变动到FsEditLog
*
* 此变量保存的内容:
* 1. 有效文件名和blockList的映射;
* 2. 合法的block集
* 3. block和机器列表的映射
* 4. 机器和blockList的映射
* 5. 更新心跳机器的 LRU 缓存
*/
protected FSNamesystem namesystem;
// namenode的角色分类,包括NameNode backup checkpoint
protected final NamenodeRole role;
// Ha状态,包括active standby backup三种实现
private volatile HAState state;
// 负责namenode的http调用
protected NameNodeHttpServer httpServer;
// 主要负责处理namenode的rpc调用
private NameNodeRpcServer rpcServer;