通过前边的文章我们已经知道,hadoop namenode启动的类org.apache.hadoop.hdfs.server.namenode.NameNode,本节内容就顺着其启动流程,查看在namenode启动的过程中其都做了哪些工作(关于一些技术细节就暂时放过)。
我们通过命令
$start-dfs.sh
启动hadoop集群的namenode和datanode,在namenode启动的过程中JVM首先加载org.apache.hadoop.hdfs.server.namenode.NameNode这个类。
从分析流程上来说我们主要关注两部分代码
static语句块,在执行主函数前
static{
HdfsConfiguration.init();
}
通过函数的名称我们也可以看出上述代码作用是初始化HDFS的配置信息。
在执行完static语句块后执行main函数,其代码如下。(分析直接写在代码的注释当中)
public static void main(String argv[]) throws Exception{
//分析传入的参数是否为帮助参数,如果是帮助的话打印帮助信息,并退出。
if(DFSUtil.parseHelpArgument(argv,NameNode.USAGE,System.out,true)){
System.exit(0);
}
try{
//格式化输出启动信息,并且创建hook(打印节点关闭信息)
StringUtils.startupShutdownMessage(NameNode.class,argv,LOG);
//创建namenode
NameNode namenode=createNameNode(argv,null);
if(namenode!=null){
//加入集群
namenode.join()
}
}catch(Throwable e){
//异常处理
LOG.error("Failed to start namenode.",e)
terminate(1,e);
}
}
namenode 在启动前首先调用HdfsConfiguration.init();初始化hdfs配置。
查看HdfsConfiguration.java代码可发现init()函数内并没有内容,是一个空函数。
那它是如何起作用的?这就要看HdfsConfiguration.java中static语句块中的内容
static {
addDeprecatedKeys();
Configuration.addDefaultResource("hdfs-default.xml");
Configuration.addDefaultResource("hdfs-site.xml");
}
我们知道我们在调用对象的一个静态方法时,JVM首先会将该类加载到内存中,并执行其内部由static标识的语句块(且仅会执行一次),然后才能执行我们所调用的方法。(这也给我们一个启示,写单例模式的时候可以用这种方法)。
所以在这里我们首次调用HdfsConfiguration.init()时会执行static语句块,之后我们再调用就就不会再执行了
(测试:我们在此修改NameNdoe.java 代码让其调用两次HdfsConfiguration.init()方法,在HdfsConfiguration.java中的static语句块内设置一个断点,观察HdfsConfiguration.java中的static语句块是否是仅执行一次)
这里有一个问题就是hadoop设计者为什么要这样设计呢?对于主要是考虑到配置文件仅需要加载一次就可以为整个系统提供服务,这是一个典型的单例模式,这种设计方法保证了多线程安全。另一方面我们看到在其static语句块中还包含了一句addDeprecatedKeys(); 这个是维护一个新旧配置命名规则的一个映射,防止部分粗心的开发者仅调用addDefaultResource()而忘记调用addDeprecatedKeys()造成旧的属性命名规则不起作用。
addDefaultResource()内部细节暂不分析,只要知道其是加载一些默认配置
进入main函数之后呢首先执行的是下面语句
if(DFSUtil.parseHelpArgument(argv,NameNode.USAGE,System.out,true)){
System.exit(0)
}
这条语句是在启动namenode前判断传入参数是否为帮助参数(-h 或者–help)
如果是的话就打印帮助信息。
我们一直在启动的时候都没有给命令行中传入参数,我们通过下面指令测试
$start-dfs.sh --help
测试可以发现,我们执行上面指令的时候,脚本根本在判断参数的时候就失败并且提前退出了,并没有进入我们的主函数。这是因为start-dfs.sh 这个脚本要启动的内容不仅仅包括namenode还有secondary namenode、datenode等等 有好多,所以你仅仅传入一个help脚本是无法判断你要输出哪一个进程的帮助信息(索性就不让你在这里使用了)那应该怎么样正确使用呢?
$hadoop-daemon.sh start namenode --help
hadoop-daemon.sh这个脚本可以指定要启动要启动哪一个进程,如果输入参数–help参数表示打印namenode的帮助信息。
接下来代码
StringUtils.startupShutdownMessage(NameNode.class,argv,LOG);
这个函数的代码不多,最终调用代码如下:
static void startupShutdownMessage(Class> clazz, String[] args,
final LogAdapter LOG) {
//使用NetUtils 获取主机名
final String hostname = NetUtils.getHostname();
//类名
final String classname = clazz.getSimpleName();
//输出日志信息
LOG.info(
toStartupShutdownString("STARTUP_MSG: ", new String[] {
"Starting " + classname,
" host = " + hostname,
" args = " + Arrays.asList(args),
" version = " + VersionInfo.getVersion(),
" classpath = " + System.getProperty("java.class.path"),
" build = " + VersionInfo.getUrl() + " -r "
+ VersionInfo.getRevision()
+ "; compiled by '" + VersionInfo.getUser()
+ "' on " + VersionInfo.getDate(),
" java = " + System.getProperty("java.version") }
)
);
if (SystemUtils.IS_OS_UNIX) {
try {
SignalLogger.INSTANCE.register(LOG);
} catch (Throwable t) {
LOG.warn("failed to register any UNIX signal loggers: ", t);
}
}
//创建钩子,节点关闭时调用打印关闭信息
ShutdownHookManager.get().addShutdownHook(
new Runnable() {
@Override
public void run() {
LOG.info(toStartupShutdownString("SHUTDOWN_MSG: ", new String[]{
"Shutting down " + classname + " at " + hostname}));
}
}, SHUTDOWN_HOOK_PRIORITY);
}