1、 namenode启动
在文档(5)中解析了namenode启动的入口方法:namenode类的main方法,它会掉用createNameNode方法,这个方法会创建一个configuration对象,然后解析传入的参数,最后根据参数不同执行不同的方法(一般情况是默认的是创建namenode类)。
在文档(6)和(7)中解析了创建configuration的具体过程,这里先跳过解析参数的方法,继续分析创建namenode的方法,创建namenode的代码片段如下:
重点是最后一行代码:new namenode。调用的构造方法如下:
public NameNode(Configuration conf) throws IOException {
this(conf, NamenodeRole.NAMENODE);
}
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);
this.haEnabled = HAUtil.isHAEnabled(conf, nsId);
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);
}
如上述代码所示,这便是namenode的构造方法,它有两个重载方法重点是第二个方法(第5行)。这段代码虽然不长,却包含了许多内容。要完全解析这段代码需要了解namenode的一些机制。这里我们先解析构造方法的整体逻辑,然后再详细分析其中的重点方法。
首先是6、7行,记录传入的参数。然后是第9行设置namenode地址。然后是第10、11行,获取nameservices和namenode的ID。然后是第12行到第15行,这里负责处理hadoop的高可用的配置。然后是第17、18行,进行那么弄得的初始化操作,这里的重点是18行的方法。然后是第20行到第24行,这里是namenode完成恢复数据的操作,在正式提供服务之前需要进入状态——standby或active。这里默认是都进入standby状态。最后是第33行标识namenode已经启动。
在之前分析中提到过namenode的主要作用是负责管理hdfs集群的元数据,它是整个集群的中心节点,集群中的所有角色都会和它进行交互。所以namenode的启动主要有两个方面的任务需要完成:第一个就是恢复节点上次停机前的数据,第二个就是启动与其他节点交互的服务。
上述任务主要都是在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);
if (NamenodeRole.NAMENODE == role) {
startHttpServer(conf);
}
this.spanReceiverHost =
SpanReceiverHost.get(conf, DFSConfigKeys.DFS_SERVER_HTRACE_PREFIX);
loadNamesystem(conf);
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);
startCommonServices(conf);
}
首先是第2行到第8行,这里检查配置参数,然后是第10、11行,这里负责处理hadoop的用户名。然后是第13、14行启动监控相关的程序。然后是第16行的if语句,这里负责启动http服务。然后是第23行负责加载元数据。然后是第25行负责创建rpc服务。然后是26行到第37行,为服务设置一些参数。然后是第39行到第41行启动pauseMonitor服务。最后是第43行启动其他的一些公共服务。
从上述代码中可以看出namenode会启动很多服务,本文会分析一些主要的服务的启动与使用。
上述代码中除了启动服务的代码外,还有一个很重要的方法——第23行的loadNamesystem方法。这个方法会加载namenode的元数据。其具体实现如下:
protected void loadNamesystem(Configuration conf) throws IOException {
this.namesystem = FSNamesystem.loadFromDisk(conf);
}
这里可以看见他实际是调用另一个类(FSNamesystem)的loadFromDisk方法。这个方法的实现如下:
static FSNamesystem loadFromDisk(Configuration conf) throws IOException {
checkConfiguration(conf);
FSImage fsImage = new FSImage(conf,
FSNamesystem.getNamespaceDirs(conf),
FSNamesystem.getNamespaceEditsDirs(conf));
FSNamesystem namesystem = new FSNamesystem(conf, fsImage, false);
StartupOption startOpt = NameNode.getStartupOption(conf);
if (startOpt == StartupOption.RECOVER) {
namesystem.setSafeMode(SafeModeAction.SAFEMODE_ENTER);
}
long loadStart = monotonicNow();
try {
namesystem.loadFSImage(startOpt);
} catch (IOException ioe) {
LOG.warn("Encountered exception loading fsimage", ioe);
fsImage.close();
throw ioe;
}
long timeTakenToLoadFSImage = monotonicNow() - loadStart;
LOG.info("Finished loading FSImage in " + timeTakenToLoadFSImage + " msecs");
NameNodeMetrics nnMetrics = NameNode.getNameNodeMetrics();
if (nnMetrics != null) {
nnMetrics.setFsImageLoadTime((int) timeTakenToLoadFSImage);
}
return namesystem;
}
这段代码的重点有三个分别是第4行、第7行和第15行。第4行会创建一个FSImage对象,第7行会创建FSNamesystem对象,最后第15行会调用FSNamesystem的loadFSImage方法加载元数据。
首先是FSImage对象,在解析namenode的元数据持久化方式的时候提到了namenode会用日志和快照两种方式来持久化元数据。其中快照文件的名称为FSImage,与这里的类同名。可想而知这个类是用来管理快照文件的。
这里的FSImage在初始化的时候传入了三个参数,首先是conf,这个是之前分析的配置文件的对象,然后是FSNamesystem的getNamespaceDirs方法的返回值,最后是getNamespaceEditsDirs方法的返回值。这两个方法都是从配置文件中获取配置的方法,这里首先看getNamespaceDirs方法:
public static Collection<URI> getNamespaceDirs(Configuration conf) {
return getStorageDirs(conf, DFS_NAMENODE_NAME_DIR_KEY);
}
这里接着调用了一个getStorageDirs方法,同时传入了一个常量DFS_NAMENODE_NAME_DIR_KEY,这个常量的定义如下:
public static final String DFS_NAMENODE_NAME_DIR_KEY = "dfs.namenode.name.dir";
这个key在之前安装hadoop的文档中提到过,在hdfs-site.xml文件会配置其value,其配置如下:
接着再看getStorageDirs方法,其内容如下:
private static Collection<URI> getStorageDirs(Configuration conf,
String propertyName) {
Collection<String> dirNames = conf.getTrimmedStringCollection(propertyName);
StartupOption startOpt = NameNode.getStartupOption(conf);
if(startOpt == StartupOption.IMPORT) {
// In case of IMPORT this will get rid of default directories
// but will retain directories specified in hdfs-site.xml
// When importing image from a checkpoint, the name-node can
// start with empty set of storage directories.
Configuration cE = new HdfsConfiguration(false);
cE.addResource("core-default.xml");
cE.addResource("core-site.xml");
cE.addResource("hdfs-default.xml");
Collection<String> dirNames2 = cE.getTrimmedStringCollection(propertyName);
dirNames.removeAll(dirNames2);
if(dirNames.isEmpty())
LOG.warn("!!! WARNING !!!" +
"\n\tThe NameNode currently runs without persistent storage." +
"\n\tAny changes to the file system meta-data may be lost." +
"\n\tRecommended actions:" +
"\n\t\t- shutdown and restart NameNode with configured \""
+ propertyName + "\" in hdfs-site.xml;" +
"\n\t\t- use Backup Node as a persistent and up-to-date storage " +
"of the file system meta-data.");
} else if (dirNames.isEmpty()) {
dirNames = Collections.singletonList(
DFSConfigKeys.DFS_NAMENODE_EDITS_DIR_DEFAULT);
}
return Util.stringCollectionAsURIs(dirNames);
}
这段代码的重点在第3行和第29行。第3行会从配置文件中读取propertyName的值,这里的propertyName之前分析了是dfs.namenode.name.dir,然后是第4行到第28行这里会根据启动方式的不同执行一些特殊操作,最后是第29行,将从配置文件中读取的字符串集合转换为URI集合。
这里我们继续分析读取配置文件的方法,即getTrimmedStringCollection方法,其内容如下:
public Collection<String> getTrimmedStringCollection(String name) {
String valueString = get(name);
if (null == valueString) {
Collection<String> empty = new ArrayList<String>();
return empty;
}
return StringUtils.getTrimmedStringCollection(valueString);
}
首先是第2行get方法,这个方法便是之前分析配置文件使用是分析的get方法。get方法的返回若为空,则返回一个空的集合;若不为空则使用一个工具类的方法将字符串按“,”拆分。
通过上面的分析,可以得出结论:FSImage的第二个参数便是配置的dfs.namenode.name.dir的值。接着继续分析第三个参数,即getNamespaceEditsDirs方法,其内容如下:
public static List<URI> getNamespaceEditsDirs(Configuration conf)
throws IOException {
return getNamespaceEditsDirs(conf, true);
}
这里继续调用了一个重载方法,其内容如下:
public static List<URI> getNamespaceEditsDirs(Configuration conf,
boolean includeShared)
throws IOException {
// Use a LinkedHashSet so that order is maintained while we de-dup
// the entries.
LinkedHashSet<URI> editsDirs = new LinkedHashSet<URI>();
if (includeShared) {
List<URI> sharedDirs = getSharedEditsDirs(conf);
// Fail until multiple shared edits directories are supported (HDFS-2782)
if (sharedDirs.size() > 1) {
throw new IOException(
"Multiple shared edits directories are not yet supported");
}
// First add the shared edits dirs. It's critical that the shared dirs
// are added first, since JournalSet syncs them in the order they are listed,
// and we need to make sure all edits are in place in the shared storage
// before they are replicated locally. See HDFS-2874.
for (URI dir : sharedDirs) {
if (!editsDirs.add(dir)) {
LOG.warn("Edits URI " + dir + " listed multiple times in " +
DFS_NAMENODE_SHARED_EDITS_DIR_KEY + ". Ignoring duplicates.");
}
}
}
// Now add the non-shared dirs.
for (URI dir : getStorageDirs(conf, DFS_NAMENODE_EDITS_DIR_KEY)) {
if (!editsDirs.add(dir)) {
LOG.warn("Edits URI " + dir + " listed multiple times in " +
DFS_NAMENODE_SHARED_EDITS_DIR_KEY + " and " +
DFS_NAMENODE_EDITS_DIR_KEY + ". Ignoring duplicates.");
}
}
if (editsDirs.isEmpty()) {
// If this is the case, no edit dirs have been explicitly configured.
// Image dirs are to be used for edits too.
return Lists.newArrayList(getNamespaceDirs(conf));
} else {
return Lists.newArrayList(editsDirs);
}
}
首先是第8行到第27行,这个if语句的判断条件是includeShared参数,这个参数是调用时传入的,由上文可知,传入的为true,所以这里会被执行。然后是第9行调用的是getSharedEditsDirs方法,该方法的内容如下:
public static List<URI> getSharedEditsDirs(Configuration conf) {
// don't use getStorageDirs here, because we want an empty default
// rather than the dir in /tmp
Collection<String> dirNames = conf.getTrimmedStringCollection(
DFS_NAMENODE_SHARED_EDITS_DIR_KEY);
return Util.stringCollectionAsURIs(dirNames);
}
第4行调用的getTrimmedStringCollection方法上文分析过主要作用是从配置文件中读取数据并将读取的字符串按“,”拆分。这里读取的配置文件的key为DFS_NAMENODE_SHARED_EDITS_DIR_KEY,其定义如下:
public static final String DFS_NAMENODE_SHARED_EDITS_DIR_KEY = "dfs.namenode.shared.edits.dir";
这个key在hdfs-site.xml中也出现过,其配置内容如下:
然后是getNamespaceEditsDirs方法的第11行到第27行,这里主要是对上述方法读取出的数据进行处理:主要是将其添加的集合中。
然后是第29行使用for循环遍历getStorageDirs方法的返回值。这里的getStorageDirs方法之前也解析过主要作用是从conf中提取数据。这里的key为DFS_NAMENODE_EDITS_DIR_KEY,其定义如下:
public static final String DFS_NAMENODE_EDITS_DIR_KEY = "dfs.namenode.edits.dir";
这个key在hdfs-site.xml中没有配置,但其有默认值这个默认值定义在hdfs-default.xml中,其内容如下:
<property>
<name>dfs.namenode.edits.dir</name>
<value>${dfs.namenode.name.dir}</value>
<description>Determines where on the local filesystem the DFS name node
should store the transaction (edits) file. If this is a comma-delimited list
of directories then the transaction file is replicated in all of the
directories, for redundancy. Default value is same as dfs.namenode.name.dir
</description>
</property>
这里可以看见它的值实际是指向了dfs.namenode.name.dir的值。
继续看getNamespaceEditsDirs方法,在for循环内部主要会将读取的数据添加到集合中。
最后这个方法的第37行到第43行会将读取到的数据返回。
至此创建FSImage传入的三个参都解析完成了,第一个参数是conf配置文件,第二个参数是配置文件中的dfs.namenode.name.dir的值,第三个参数是配置文件中dfs.namenode.shared.edits.dir和dfs.namenode.edits.dir的值。而FSImage的构造方法如下:
protected FSImage(Configuration conf,
Collection<URI> imageDirs,
List<URI> editsDirs)
throws IOException {
this.conf = conf;
storage = new NNStorage(conf, imageDirs, editsDirs);
if(conf.getBoolean(DFSConfigKeys.DFS_NAMENODE_NAME_DIR_RESTORE_KEY,
DFSConfigKeys.DFS_NAMENODE_NAME_DIR_RESTORE_DEFAULT)) {
storage.setRestoreFailedStorage(true);
}
this.quotaInitThreads = conf.getInt(
DFSConfigKeys.DFS_NAMENODE_QUOTA_INIT_THREADS_KEY,
DFSConfigKeys.DFS_NAMENODE_QUOTA_INIT_THREADS_DEFAULT);
this.editLog = new FSEditLog(conf, storage, editsDirs);
archivalManager = new NNStorageRetentionManager(conf, storage, editLog);
}
这里主要是对几个参数进行赋值。