名字节点启动和停止
安全模式
名字节点启动时,内存中一片空白,第一要紧的事情就是把命名空间镜像加载到内存,并应用镜像编辑日志,这个过程结束时,会创建一个新的检查点,包括新命名空间镜像和一个空的编辑日志。完成上诉工作后,名字节点开始监听IPC和HTTP请求,虽然这个时候名字节点已经对外提供服务,但它只是为客户端提供了一个只读视图,这种名字节点的只读模式为安全模式。
继续上面的过程,在名字节点第一关系建立起来以后,需要处理数据节点启动时的若干远程调用,以建立名字节点第二关系。在安全模式先,需要给名字节点一些时间,使名字节点获得足够多的数据节点,数据块也能获得足够多的保存数据块的数据节点的信息,以高效地运行文件系统。如果名字节点没有等到足够多的数据节点,大量的数据块会初夏副本数不足的情况,进入复制队列并最终产生复制请求。这在很多情况下显然是没有必要的,而且还会浪费系统资源。从前面的代码分析,我们知道,在安全模式下,名字节点不会向数据节点下发数据块复制或数据块删除指令。
当收集到足够多的第二关系信息后,名字节点会离开安全模式,进入正常工作状态,允许客户端和系统自身对文件系统进行修改。足够多的信息指的是文件系统中一定比例的数据块副本信息达到最低副本水平,默认情况下,需要系统95%的数据块在第二关系中有一个副本的位置信息。名字节点离开安全模式前,还可以通过配置安全模式等待时间,在满足上述离开安全模式的副本条件后,让名字节点继续等待一段时间,等待时间的默认值为0,也就是立即离开安全模式。
安全模式中的配置项:
dfs.replication.min: 默认值为 1 对应SafeModeInfo中对应变量:safeReplication 最低副本水平数/系统最小副本数,也用于写操作中,若completeFileInternal()方法中.
dfs.safemode.threshold.pct 默认值为 0.95 对应SafeModeInfo中对应变量:threshold 离开安全模式时系统满足最低副本水平的副本比列
dfs.safemode.extension 默认值为0 对应SafeModeInfo中对应变量:extension 安全模式等待时间,在满足最小副本水平提交后,离开安全模式的时间,单位是毫秒。
HDFS安全模式的需求总结如下:
1)处于安全模式下,HDFS只提供系统的只读视图,不能进行修改。
2)名字节点启动时,根据配置,检查第二关系中数据块的副本信息,满足条件时离开安全模式。
3)通过命令行支持安全模式的状态查询、等待和设置。
名字节点的启动
NameNode.main()是名字节点启动时的入口,和数据节点一样,这个main()方法通过类的静态方法createNameNode()创建了一个NameNode对象,顺利创建对象后通过NameNode.join()等待对象执行结束。代码如下:
/**
*/
public static void main(String argv[]) throws Exception {
try {
StringUtils.startupShutdownMessage(NameNode.class, argv, LOG);
NameNode namenode = createNameNode(argv, null);
if (namenode != null)
namenode.join();
} catch (Throwable e) {
LOG.error(StringUtils.stringifyException(e));
System.exit(-1);
}
}
/**
* Wait for service to finish.
* (Normally, it runs forever.)
*/
public void join() {
try {
this.server.join();
} catch (InterruptedException ie) {
}
}
public static NameNode createNameNode(String argv[],
Configuration conf) throws IOException {
if (conf == null)
conf = new Configuration();
StartupOption startOpt = parseArguments(argv);
if (startOpt == null) {
printUsage();
return null;
}
setStartupOption(conf, startOpt);
switch (startOpt) {
case FORMAT:
boolean aborted = format(conf, true);
System.exit(aborted ? 1 : 0);
case FINALIZE:
aborted = finalize(conf, true);
System.exit(aborted ? 1 : 0);
default:
}
DefaultMetricsSystem.initialize("NameNode");
NameNode namenode = new NameNode(conf);
return namenode;
}
createNameNode()方法会处理名字节点的启动选项,这些选项包括:
FORMAT:格式化,和数据节点不同,启动HDFS集群前需要对名字节点进行格式化,以建立或重新建立名字节点文件结构。
REGULAR:正常启动。
UPGRADE/ROLLBACK/FINALIZE:升级、升级回滚或升级提交
IMPORT:恢复名字节点上的元数据到某一个检查点。
NameNode类的实现中,将大量具体的工作交由FSNamesystem完成,初始化也是一样的。创建名字节点的FSNamesystem对象时,会完成如下工作:
1)通过配置项为FSNamesystem的成员变量赋值。
2)创建FSDirectory对象,即名字节点第一关系的门面。
3)通过FSDirectory.loadFSImage(),加载命名空间镜像并应用编辑日志。
4)创建安全模式管理对象SafeModeInfo(),并调用setBlockTotal()更新SafeModeInfo的成员变量blockTotal,调用结束后,名字节点一般应处于安全模式。
5)创建并启动心跳检查线程、租约检查线程、数据块复制/删除线程。
6)读入数据节点的include文件和exclude文件,创建并启动数据节点撤销检查线程。
7)创建网络布局感知需要的dnsToSwitchMapping对象,如果可能,分析include文件中包含的主机位置。
8)读入配置项${fs.deafult.name}
FSNamesystem.initialize()让我们回顾了名字节点的主要组成部分,如名字节点第一关系的FSDirectory、命名空间镜像和编辑日志,名字节点第二关系的数据节点管理和数据块副本复制、删除,安全模式等。构造玩FSNamesystem对象后,NameNode.initialize()打开IPC服务器,名字节点就开始对外提供服务了。代码如下:
/**
* FSNamesystem constructor.
*/
FSNamesystem(NameNode nn, Configuration conf) throws IOException {
try {
initialize(nn, conf);
} catch(IOException e) {
LOG.error(getClass().getSimpleName() + " initialization failed.", e);
close();
throw e;
}
}
/**
* Initialize FSNamesystem.
*/
private void initialize(NameNode nn, Configuration conf) throws IOException {
this.systemStart = now();
setConfigurationParameters(conf);
dtSecretManager = createDelegationTokenSecretManager(conf);
this.nameNodeAddress = nn.getNameNodeAddress();
this.registerMBean(conf); // register the MBean for the FSNamesystemStutus
this.dir = new FSDirectory(this, conf);
StartupOption startOpt = NameNode.getStartupOption(conf);
this.dir.loadFSImage(getNamespaceDirs(conf),
getNamespaceEditsDirs(conf), startOpt);
long timeTakenToLoadFSImage = now() - systemStart;
LOG.info("Finished loading FSImage in " + timeTakenToLoadFSImage + " msecs");
NameNode.getNameNodeMetrics().setFsImageLoadTime(timeTakenToLoadFSImage);
this.safeMode = new SafeModeInfo(conf);
setBlockTotal();
pendingReplications = new PendingReplicationBlocks(
conf.getInt("dfs.replication.pending.timeout.sec",
-1) * 1000L);
if (isAccessTokenEnabled) {
accessTokenHandler = new BlockTokenSecretManager(true,
accessKeyUpdateInterval, accessTokenLifetime);
}
this.hbthread = new Daemon(new HeartbeatMonitor());
this.lmthread = new Daemon(leaseManager.new Monitor());
this.replmon = new ReplicationMonitor();
this.replthread = new Daemon(replmon);
hbthread.start();
lmthread.start();
replthread.start();
this.hostsReader = new HostsFileReader(conf.get("dfs.hosts",""),
conf.get("dfs.hosts.exclude",""));
this.dnthread = new Daemon(new DecommissionManager(this).new Monitor(
conf.getInt("dfs.namenode.decommission.interval", 30),
conf.getInt("dfs.namenode.decommission.nodes.per.interval", 5)));
dnthread.start();
this.dnsToSwitchMapping = ReflectionUtils.newInstance(
conf.getClass("topology.node.switch.mapping.impl", ScriptBasedMapping.class,
DNSToSwitchMapping.class), conf);
/* If the dns to swith mapping supports cache, resolve network
* locations of those hosts in the include list,
* and store the mapping in the cache; so future calls to resolve
* will be fast.
*/
if (dnsToSwitchMapping instanceof CachedDNSToSwitchMapping) {
dnsToSwitchMapping.resolve(new ArrayList<String>(hostsReader.getHosts()));
}
InetSocketAddress socAddr = NameNode.getAddress(conf);
this.nameNodeHostName = socAddr.getHostName();
registerWith(DefaultMetricsSystem.INSTANCE);
}
FSNamesystem和NameNode的构造函数调用各自的initialize()方法失败时,都有相应的清理函数,释放已经初始化的资源,具体参考FSNamesystem.close()和NameNode.stop()实现。
名字节点的停止
相对于启动,停止名字节点不需要做太多的工作:名字节点第一关系在执行的过程中会不断通过日志将更新持久化,而第二关系则可通过数据节点上保存的数据块信息回复。所以,名字节点停止时,只是执行打印的逻辑时,由StringUtils.startupShutdownMessage()方法,通过Runtime.getRuntime().addShutdownHook()添加到关闭挂钩中,由Java虚拟机在退出时执行。
版权申明:本文部分摘自【蔡斌、陈湘萍】所著【Hadoop技术内幕 深入解析Hadoop Common和HDFS架构设计与实现原理】一书,仅作为学习笔记,用于技术交流,其商业版权由原作者保留,推荐大家购买图书研究,转载请保留原作者,谢谢!