NameNodeHttpServer启动源码剖析,这一部分主要按以下步骤进行:

  一、源码调用分析

  二、伪代码调用流程梳理

  三、http server服务流程图解

第一步,源码调用分析

  前一篇文章已经锁定到了NameNode.java类文件,搜索找到main(),可以看到代码只有寥寥几行,再筛除掉一些参数校验以及try-catch逻辑代码,

  剩下的核心的代码甚至只有两行,如下:



1   public static void main(String argv[]) throws Exception {
 2     if (DFSUtil.parseHelpArgument(argv, NameNode.USAGE, System.out, true)) {
 3       System.exit(0);
 4     }
 5 
 6     try {
 7       StringUtils.startupShutdownMessage(NameNode.class, argv, LOG);
 8       NameNode namenode = createNameNode(argv, null);
 9       if (namenode != null) {
10         namenode.join();
11       }
12     } catch (Throwable e) {
13       LOG.fatal("Failed to start namenode.", e);
14       terminate(1, e);
15     }
16   }



  根据main()里面的代码,可以得知主流程是:1.创建NameNode对象,2.调用join()方法。然后深入到createNameNode()方法中,如下:



public static NameNode createNameNode(String argv[], Configuration conf)
    throws IOException {
  LOG.info("createNameNode " + Arrays.asList(argv));
  if (conf == null)
    conf = new HdfsConfiguration();
  ......// Parse the rest, NN specific args.
  StartupOption startOpt = parseArguments(argv);
  if (startOpt == null) {
    printUsage(System.err);
    return null;
  }
  // 重要代码:设置解析出来的参数。
  setStartupOption(conf, startOpt);

  switch (startOpt) {
    // 如果传递的是-format,就是告诉namenode要做目录结构的格式化,其他的也同理。
    case FORMAT: {
      boolean aborted = format(conf, startOpt.getForceFormat(),
          startOpt.getInteractiveFormat());
      terminate(aborted ? 1 : 0);
      return null; // avoid javac warning
    }
    ......
    case ROLLBACK: {
      boolean aborted = doRollback(conf, true);
      terminate(aborted ? 1 : 0);
      return null; // avoid warning
    }
    ......
    default: {
      // 核心代码:如果正常情况下启动一个NameNode进程,代码会执行到此处,实例化一个NameNode对象。
      DefaultMetricsSystem.initialize("NameNode");
      return new NameNode(conf);
    }
  }
}



  这段代码看着很长,其实最重要的也就标粗的两行:

  • setStartupOption(conf, startOpt);

      这行代码是用来设置解析出来的参数。比如执行一条shell命令:hdfs namenode -format,其中的-format就是作为参数传递进来

      进行解析,然后经过下面的switch()进行判断,进入到格式化的代码部分。其他的Hadoop命令参数比如-rollback也同理,都会进入

      到这个switch(),根据传入的参数去执行相应的功能代码。

  • new NameNode(conf);

      switch()里前面的选择都是一些NameNode的功能模块,比如初始化、回滚、产生集群id等,而正常情况下启动NameNode进程会进入

      到default模块,在这里会实例化一个NameNode对象。

  接下来,就继续dive into到NameNode的构造方法中:



protected NameNode(Configuration conf, NamenodeRole role) 
    throws IOException {
  // 初始化一些参数及配置
  this.conf = conf;
  this.role = role;
  setClientNamenodeAddress(conf);
  ......
  try {
    initializeGenericKeys(conf, nsId, namenodeId);
    // 核心代码:初始化NameNode实例对象。
    initialize(conf);
    try {
      ......
  } catch (HadoopIllegalArgumentException e) {
    this.stop();
    throw e;
  }
  this.started.set(true);
}



  可以看到除了上面的一些参数配置之外,最重要的一行代码就是initialize()方法了。也可以根据NameNode的注释说明结合集群启动命令等

  推测到,NameNodeServer的代码肯定是在这个初始化方法里面。继续往下深入,



protected void initialize(Configuration conf) throws IOException {
  // 可以通过找到下面变量名的映射,在hdfs-default.xml中找到对应的配置
  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);
    }
  }

  ......

  // 核心代码:启动HttpServer
  if (NamenodeRole.NAMENODE == role) {
    startHttpServer(conf);
  }

  this.spanReceiverHost = SpanReceiverHost.getInstance(conf);

  // 核心代码:后面FSNamesystem初始化篇研究
  loadNamesystem(conf);

  // 核心代码:后面rpc server启动流程篇研究
  rpcServer = createRpcServer(conf);
  
  ......
  
  startCommonServices(conf);
}



  这一段代码里面,核心代码有三行,但是目前只针对http server启动流程进行挖掘,下面两行核心代码会有专门的篇章去针对性的研究。

继续深入startHttpServer()方法,



private void startHttpServer(final Configuration conf) throws IOException {
    httpServer = new NameNodeHttpServer(conf, this, getHttpServerBindAddress(conf));
    // 核心代码
    httpServer.start();
    httpServer.setStartupProgress(startupProgress);
  }



  继续深入httpServer.start()方法,看看里面都做了哪些事情,



void start() throws IOException {
  HttpConfig.Policy policy = DFSUtil.getHttpPolicy(conf);
  final String infoHost = bindAddress.getHostName();

  // 重要代码
  final InetSocketAddress httpAddr = bindAddress;
  final String httpsAddrString = conf.get(
      DFSConfigKeys.DFS_NAMENODE_HTTPS_ADDRESS_KEY,
      DFSConfigKeys.DFS_NAMENODE_HTTPS_ADDRESS_DEFAULT);
  InetSocketAddress httpsAddr = NetUtils.createSocketAddr(httpsAddrString);

  ......

  // 重要代码
  HttpServer2.Builder builder = DFSUtil.httpServerTemplateForNNAndJN(conf,
      httpAddr, httpsAddr, "hdfs",
      DFSConfigKeys.DFS_NAMENODE_KERBEROS_INTERNAL_SPNEGO_PRINCIPAL_KEY,
      DFSConfigKeys.DFS_NAMENODE_KEYTAB_FILE_KEY);

  httpServer = builder.build();

  ......

  initWebHdfs(conf);

  httpServer.setAttribute(NAMENODE_ATTRIBUTE_KEY, nn);
  httpServer.setAttribute(JspHelper.CURRENT_CONF, conf);

  // 核心代码:HttpServer2绑定了一堆servlet,定义好了接收哪些http请求,接收到了请求由谁来处理。
  setupServlets(httpServer, conf);
  // 核心代码:启动httpServer
  httpServer.start();

  ......
}



  这段代码中的bindAddress,经过一步一步的溯源,可以对应到hdfs-default.xml中的这段属性:



<property>
  <name>dfs.namenode.http-address</name>
  <value>0.0.0.0:50070</value>
  <description>
    The address and the base port where the dfs namenode web ui will listen on.
  </description>
</property>



  可以得知bindAddress就是http server绑定在哪个端口上,默认是0.0.0.0:50070,当然也可以自己配置。结合以往的集群启动经验,启动成功后

  我们一般会在浏览器访问http://xx.xx.xx.xx:50070这个hdfs管理工作台,可以查看集群的状态、元数据、目录结构、block信息等。这其实就是

  NameNode的http server提供的服务。

  具体http server是什么?往下看会有这么一行代码:

HttpServer2.Builder builder = ....;
  httpServer = builder.build();

  这段代码指示了http server是具体的哪个类——HttpServer2.java。这个类是Hadoop实现的一套http服务,用于接收http请求。并且这一块是

  Hadoop抽离出来独立的一个模块,在hadoop-common包中,是整个Hadoop框架通用的一套http server服务。具体怎么实现的不在本篇研究

  范围,就不做深究了。

  继续往下看,setupServlets(); 这一行是核心代码,点进去发现是绑定了各种参数的servlet。这些servlet定义了接收哪些http请求,并且接收到

  请求之后由哪个servlet进行处理。举个栗子,我们在浏览器请求了http://xx.xx.xx.xx:50070/listPath?path=/user/warehouse,此时HttpServer2

  接收到请求,然后转发给对应的处理listPath功能的servlet,servlet处理完之后再将结果返回。

  最后,就是httpServer.start()了,代码执行到此处会启动httpServer,然后客户端都可以通过http请求进行访问了。

第二步,伪代码调用流程梳理。



NameNode.main() // 入口函数
    |——createNameNode(); // 通过new NameNode()进行实例化
          |——initialize(); // 方法进行初始化操作
               |——startHttpServer(); // 启动HttpServer
                    |——httpServer = new NameNodeHttpServer(); // 通过实例化HttpServer进行启动
                    |——httpServer.start(); // 具体的一些启动细节:servlet等配置
                         |——setupServlets();
                         |——httpServer.start();
    |——join()



  现在,对上述的整个流程进行一个梳理,

  1. NameNode.main() 入口函数
  2. createNameNode() 方法通过new NameNode()进行实例化
  3. 实例化的时候,会通过initialize()方法进行初始化操作
  4. 初始化操作主要包含了启动HttpServer、加载元数据信息、启动RpcServer这三部分。后面两部分待后续篇幅进行研究。

第三步,http server服务流程图解。

hadoop启动namenode节点命令_实例化