Hadoop3.2.1 【 HDFS 】源码分析 : Namenode[非HA]启动解析

 

一. 前言

Namenode在hadoop的hdfs体系中的作用是毋庸置疑的.本文主要分析namenode的启动过程.梳理一下Namenode的逻辑关系.

Namenode实体在代码实现中主要对应于三个类, 即NameNode类、NameNodeRpcServer类以及FSNamesystem类。

NameNodeRpcServer类用于接收和处理所有的RPC请求,

FSNamesystem类负责实现Namenode的所有逻辑,

NameNode类则负责管理Namenode配置、 RPC接口以及HTTP接口等。 

 

二.启动入口

Namenode启动的入口这个很容易找,可以从启动命令的shell脚本开始找,比如从${HADOOP_HOME}/sbin/start-dfs.sh 脚本开始找起.

怎么找的话,这个我就不唠叨了, 最终的执行命令如下:

/Library/java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/bin/java 
 -Dproc_namenode  
 -Djava.net.preferIPv4Stack=true 
 -Djava.security.krb5.realm=  
 -Djava.security.krb5.kdc=  
 -Djava.security.krb5.conf=  
 -Dhdfs.audit.logger=INFO,NullAppender 
 -Dhadoop.security.logger=INFO,RFAS 
 -Dyarn.log.dir=/tools/hadoop-3.2.1/logs 
 -Dyarn.log.file=hadoop-sysadmin-namenode-bogon.log 
 -Dyarn.home.dir=/tools/hadoop-3.2.1 
 -Dyarn.root.logger=INFO,console 
 -Djava.library.path=/tools/hadoop-3.2.1/lib/native 
 -Dhadoop.log.dir=/tools/hadoop-3.2.1/logs 
 -Dhadoop.log.file=hadoop-sysadmin-namenode-bogon.log 
 -Dhadoop.home.dir=/tools/hadoop-3.2.1 
 -Dhadoop.id.str=sysadmin 
 -Dhadoop.root.logger=INFO,RFA 
 -Dhadoop.policy.file=hadoop-policy.xml  
org.apache.hadoop.hdfs.server.namenode.NameNode  

所以呢,最重要的就是Namenode的启动类, 我们可以看到直接调用的是这个类,然后启动就可以了.

org.apache.hadoop.hdfs.server.namenode.NameNode 

对应的main方法入口:

Hadoop3.2.1 【 HDFS 】源码分析 : Namenode[非HA]启动解析_第1张图片

其实我们最关心的就是这句: 

NameNode namenode = createNameNode(argv, null);

三.创建NameNode

NameNode是通过createNameNode方法进行创建的,name这个方法里面都干了啥呢???

我们继续看.

 


  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.
    //解析命令行的参数
    StartupOption startOpt = parseArguments(argv);
    if (startOpt == null) {
      printUsage(System.err);
      return null;
    }
    setStartupOption(conf, startOpt);

    boolean aborted = false;

    //根据启动选项调用对应的方法执行操作
    switch (startOpt) {

    //格式化当前Namenode, 调用format()方法执行格式化操作
    case FORMAT:
      aborted = format(conf, startOpt.getForceFormat(),
          startOpt.getInteractiveFormat());
      terminate(aborted ? 1 : 0);
      return null; // avoid javac warning
    case GENCLUSTERID:
      System.err.println("Generating new cluster id:");
      System.out.println(NNStorage.newClusterID());
      terminate(0);
      return null;

    //回滚上一次升级, 调用doRollback()方法执行回滚操作。
    case ROLLBACK:
      aborted = doRollback(conf, true);
      terminate(aborted ? 1 : 0);
      return null; // avoid warning

    // 拷贝Active Namenode的最新命名空间数据到StandbyNamenode,
    // 调用BootstrapStandby.run()方法执行操作
    case BOOTSTRAPSTANDBY:
      String[] toolArgs = Arrays.copyOfRange(argv, 1, argv.length);
      int rc = BootstrapStandby.run(toolArgs, conf);
      terminate(rc);
      return null; // avoid warning

    //初始化editlog的共享存储空间, 并从Active
    //Namenode中拷贝足够的editlog数据, 使得Standby节点能够顺利启动。 这里调用
    //了静态方法initializeSharedEdits()执行操作
    case INITIALIZESHAREDEDITS:
      aborted = initializeSharedEdits(conf,
          startOpt.getForceFormat(),
          startOpt.getInteractiveFormat());
      terminate(aborted ? 1 : 0);
      return null; // avoid warning
    // 启动backup节点, 这里直接构造一个BackupNode对象并返回。
    // 啥也不错 ??
    case BACKUP:


    //启动checkpoint节点, 也是直接构造BackupNode对象并返回。
    case CHECKPOINT:
      NamenodeRole role = startOpt.toNodeRole();
      DefaultMetricsSystem.initialize(role.toString().replace(" ", ""));
      return new BackupNode(conf, role);

    //恢复损坏的元数据以及文件系统, 这里调用了doRecovery()方法执行操作
    case RECOVER:
      NameNode.doRecovery(startOpt, conf);
      return null;

    //确认配置文件夹存在, 并且打印fsimage文件和文件系统的元数据版本
    case METADATAVERSION:
      printMetadataVersion(conf);
      terminate(0);
      return null; // avoid javac warning

    //升级Namenode, 升级完成后关闭Namenode。
    case UPGRADEONLY:
      DefaultMetricsSystem.initialize("NameNode");
      new NameNode(conf);
      terminate(0);
      return null;


    //在默认情况下直接构造NameNode对象并返回
    default:
      // 初始化 度量服务
      DefaultMetricsSystem.initialize("NameNode");
      return new NameNode(conf);
    }
  }

 

这里最重要的是根据输入的参数来执行不同的任务.任务类型如下:

序号 类型 描述
1 FORMAT 格式化当前Namenode, 调用format()方法执行格式化操作。
2 GENCLUSTERID 生成集群id

3

ROLLBACK

回滚上一次升级, 调用doRollback()方法执行回滚操作。
 

BOOTSTRAPSTANDBY

拷贝Active Namenode的最新命名空间数据到StandbyNamenode, 调用BootstrapStandby.run()方法执行操作。

5

INITIALIZESHAREDEDITS

初始化editlog的共享存储空间, 并从ActiveNamenode中拷贝足够的editlog数据, 使得Standby节点能够顺利启动。 这里调用了静态方法initializeSharedEdits()执行操作

6

BACKUP

3.2.1代码啥也没写,作用未知....

7

CHECKPOINT

启动checkpoint节点, 也是直接构造BackupNode对象并返回。

8

RECOVER

恢复损坏的元数据以及文件系统, 这里调用了doRecovery()方法执行操作。

9

METADATAVERSION

确认配置文件夹存在, 并且打印fsimage文件和文件系统的元数据版本。

10

UPGRADEONLY

升级Namenode, 升级完成后关闭Namenode。

11

default

构建NameNode

以为我们看的是启动方法,name就直接关注 new NameNode(conf); 方法就可以了

 

四. 构造方法

NameNode的构建是通过其构造方法NameNode(Configuration conf) 作为入口进行创建的.

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);

    // 根据配置确认是否开启了HA
    String namenodeId = HAUtil.getNameNodeId(conf, nsId);

    //fs.defaultFS  localhost:8020
    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);

    //非HA ==> ACTIVE_STATE
    state = createHAState(getStartupOption(conf));

    //This is used only by tests at the moment.
    this.allowStaleStandbyReads = HAUtil.shouldAllowStandbyReads(conf);

    this.haContext = createHAContext();
    try {

      initializeGenericKeys(conf, nsId, namenodeId);
      //执行初始化操作!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
      initialize(getConf());
      try {
        haContext.writeLock();

        //初始化完成后, Namenode进入Standby状态
        //在这里会开启StandbyCheckpointer里面的
        //  checkpointer 线程. 定时合并&处理images文件
        state.prepareToEnterState(haContext);
        state.enterState(haContext);
      } finally {
        haContext.writeUnlock();
      }
    } catch (IOException e) {
      //出现异常, 直接停止Namenode服务
      this.stopAtException(e);
      throw e;
    } catch (HadoopIllegalArgumentException e) {
      //直接停止Namenode服务
      this.stopAtException(e);
      throw e;
    }
    this.started.set(true);
  }

在这里其实就是拿到一些配置, 比如: 

Hadoop3.2.1 【 HDFS 】源码分析 : Namenode[非HA]启动解析_第2张图片

 

然后里面最重要的就是调用了初始化的方法  initialize(getConf());

 

五. 初始化 initialize

initialize(getConf()); 是namenode的初始化方法. Namenode的一些重要服务的启动都是通过他来完成的.

/**
   * Initialize name-node.
   * 
   * @param conf the configuration
   */
  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);


    //构造JvmPauseMonitor对象, 并启动
    pauseMonitor = new JvmPauseMonitor();
    pauseMonitor.init(conf);
    pauseMonitor.start();
    metrics.getJvmMetrics().setPauseMonitor(pauseMonitor);

    //启动HTTP服务
    if (NamenodeRole.NAMENODE == role) {
      startHttpServer(conf);
    }


    // 初始化FSNamesystem
    // NameNode将对文件系统的管理都委托给了FSNamesystem对象,
    // NameNode会调用FSNamesystem.loadFromDisk()创建FSNamesystem对象。
    //
    // FSNamesystem.loadFromDisk()首先调用构造方法构造FSNamesystem对象,
    // 然后将fsimage以及editlog文件加载到命名空间中。
    loadNamesystem(conf);


    startAliasMapServerIfNecessary(conf);


    //创建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());
    }

    //启动httpServer以及 rpcServer
    startCommonServices(conf);

    //启动计时器定期将NameNode度量写入日志文件。此行为可由配置禁用。
    startMetricsLogger(conf);
  }

着这里面一共有三个重要的步骤.

1. 启动http服务 :   startHttpServer(conf);

2. 初始化FSNamesystem :   loadNamesystem(conf);

3.创建RPC服务 :  createRpcServer(conf);

 

 

 

六.构建http服务 [startHttpServer ]

其实就是构建一个NameNodeHttpServer对象, 然后启动就可以了.



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

如图,构建初始化的时候,绑定地址为  0.0.0.0: 9870

Hadoop3.2.1 【 HDFS 】源码分析 : Namenode[非HA]启动解析_第3张图片

这个类,我们后期会进行详细讲解.下面是start 方法,简单看一下吧. 

/**
   * @see DFSUtil#getHttpPolicy(org.apache.hadoop.conf.Configuration)
   * for information related to the different configuration options and
   * Http Policy is decided.
   */
  void start() throws IOException {

    //获取测了测
    HttpConfig.Policy policy = DFSUtil.getHttpPolicy(conf);

    //获取服务器host
    final String infoHost = bindAddress.getHostName();

    // 获取绑定地址
    final InetSocketAddress httpAddr = bindAddress;

    //获取服务地址  0.0.0.0:9871
    final String httpsAddrString = conf.getTrimmed(
        DFSConfigKeys.DFS_NAMENODE_HTTPS_ADDRESS_KEY,
        DFSConfigKeys.DFS_NAMENODE_HTTPS_ADDRESS_DEFAULT);
    //构建 网络InetSocketAddress 服务:
    InetSocketAddress httpsAddr = NetUtils.createSocketAddr(httpsAddrString);

    if (httpsAddr != null) {
      // If DFS_NAMENODE_HTTPS_BIND_HOST_KEY exists then it overrides the
      // host name portion of DFS_NAMENODE_HTTPS_ADDRESS_KEY.

      // 绑定地址 如果dfs.namenode.https-bind-host已经绑定了地址的话,将会覆盖掉之前创建的
      //
      final String bindHost = conf.getTrimmed(DFSConfigKeys.DFS_NAMENODE_HTTPS_BIND_HOST_KEY);
      if (bindHost != null && !bindHost.isEmpty()) {
        httpsAddr = new InetSocketAddress(bindHost, httpsAddr.getPort());
      }
    }

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

    final boolean xFrameEnabled = conf.getBoolean(
        DFSConfigKeys.DFS_XFRAME_OPTION_ENABLED,
        DFSConfigKeys.DFS_XFRAME_OPTION_ENABLED_DEFAULT);

    final String xFrameOptionValue = conf.getTrimmed(
        DFSConfigKeys.DFS_XFRAME_OPTION_VALUE,
        DFSConfigKeys.DFS_XFRAME_OPTION_VALUE_DEFAULT);

    builder.configureXFrame(xFrameEnabled).setXFrameOption(xFrameOptionValue);

    //构建http 服务
    httpServer = builder.build();

    if (policy.isHttpsEnabled()) {
      // assume same ssl port for all datanodes
      InetSocketAddress datanodeSslPort = NetUtils.createSocketAddr(conf.getTrimmed(
          DFSConfigKeys.DFS_DATANODE_HTTPS_ADDRESS_KEY, infoHost + ":"
              + DFSConfigKeys.DFS_DATANODE_HTTPS_DEFAULT_PORT));
      httpServer.setAttribute(DFSConfigKeys.DFS_DATANODE_HTTPS_PORT_KEY,
          datanodeSslPort.getPort());
    }

    initWebHdfs(conf, bindAddress.getHostName(), httpServer,
        NamenodeWebHdfsMethods.class.getPackage().getName());

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

    int connIdx = 0;
    if (policy.isHttpEnabled()) {
      httpAddress = httpServer.getConnectorAddress(connIdx++);
      conf.set(DFSConfigKeys.DFS_NAMENODE_HTTP_ADDRESS_KEY,
          NetUtils.getHostPortString(httpAddress));
    }

    if (policy.isHttpsEnabled()) {
      httpsAddress = httpServer.getConnectorAddress(connIdx);
      conf.set(DFSConfigKeys.DFS_NAMENODE_HTTPS_ADDRESS_KEY,
          NetUtils.getHostPortString(httpsAddress));
    }
  }

 

 

 

七.构建FSNamesystem

从磁盘中加载 FSNamesystem

 protected void loadNamesystem(Configuration conf) throws IOException {
    // 从磁盘中加载 FSNamesystem
    this.namesystem = FSNamesystem.loadFromDisk(conf);
  }

 

在这里,是调用FSNamesystem.loadFromDisk(conf); 加载文件系统信息.

/**
   * 从配置中加载image和edits目录  实例化FSNamesystem
   * Instantiates an FSNamesystem loaded from the image and edits
   * directories specified in the passed Configuration.
   *
   * @param conf the Configuration which specifies the storage directories
   *             from which to load
   * @return an FSNamesystem which contains the loaded namespace
   * @throws IOException if loading fails
   */
  static FSNamesystem loadFromDisk(Configuration conf) throws IOException {

    checkConfiguration(conf);

    // 构建 FSImage
    FSImage fsImage = new FSImage(conf,
        FSNamesystem.getNamespaceDirs(conf),  //dfs.namenode.name.dir
        FSNamesystem.getNamespaceEditsDirs(conf));


    //FSNamesystem的构造方法比较长, 但是逻辑很简单, 主要是从配置文件中获取参数,
    // 然后构造FSDirectory、 BlockManager、 SnapshotManager、 CacheManager、SafeModeInfo等对象。
    //
    // 需要注意的是, FSNamesystem的构造方法并不从磁盘上加载fsimage以及editlog文件,
    // 这些操作是在创建FSNamesystem对象成功后, 在loadFromDisk()中执行的。

    // 如果FSNamesystem初始化失败,
    // 则会调用FSNamesystem.close()方法
    // 关闭FSNamesystem启动的所有服务
    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 {

      //加载fsimage以及editlog文件
      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);
    }

    namesystem.getFSDirectory().createReservedStatuses(namesystem.getCTime());

    return namesystem;
  }

 

首先是检查配置文件中的配置: checkConfiguration(conf);

根据配置文件中的配置信息入下:

Hadoop3.2.1 【 HDFS 】源码分析 : Namenode[非HA]启动解析_第4张图片

 

接下来,我们就会看到下面的代码构建FSImage

// 构建 FSImage
    FSImage fsImage = new FSImage(conf,
        FSNamesystem.getNamespaceDirs(conf),  //dfs.namenode.name.dir
        FSNamesystem.getNamespaceEditsDirs(conf));

我们debug看一下,FSImage的初始化方法:

Hadoop3.2.1 【 HDFS 】源码分析 : Namenode[非HA]启动解析_第5张图片

 

在这里,我们可以看到设置的image的存储目录和edits的存储目录: 这里我指定的是: file:/tools/hadoop-3.2.1/data/namenode

接下来就是构建 FSEditLog

Hadoop3.2.1 【 HDFS 】源码分析 : Namenode[非HA]启动解析_第6张图片

 

我们再回到 loadFromDisk 方法,到这里已经构建完 FSImage , 接下来就是构建 FSNamesystem, 加载fsimage以及editlog文件

Hadoop3.2.1 【 HDFS 】源码分析 : Namenode[非HA]启动解析_第7张图片

FSNamesystem这个先不做详细介绍,后面会有章节进行详细说明.

 

 

 

八.构建NameNodeRpcServer服务 [startHttpServer ]

 

/**
   * Create the RPC server implementation. Used as an extension point for the
   * BackupNode.
   */
  protected NameNodeRpcServer createRpcServer(Configuration conf)
      throws IOException {
    return new NameNodeRpcServer(conf, this);
  }

创建RPC服务,这个供程序调用, 我们常用的是8020端口 

Hadoop3.2.1 【 HDFS 】源码分析 : Namenode[非HA]启动解析_第8张图片

 

创建完RPC服务器,调用startCommonServices方法进行启动服务

//启动httpServer以及 rpcServer
    startCommonServices(conf);

 

 

 

九.修改Namenode的状态为active

    try {
        haContext.writeLock();

        //初始化完成后, Namenode进入Standby状态
        //在这里会开启StandbyCheckpointer里面的
        //  checkpointer 线程. 定时合并&处理images文件
        state.prepareToEnterState(haContext);
        state.enterState(haContext);
      } finally {
        haContext.writeUnlock();
      }

 

十.web页面访问

到这里我们就可以访问web页面了

Hadoop3.2.1 【 HDFS 】源码分析 : Namenode[非HA]启动解析_第9张图片

 

 

 

 

 

 

感谢 : 

        Hadoop 2.X HDFS源码剖析-徐鹏

        深度剖析Hadoop HDFS -林意群

 

 

 

 

 

你可能感兴趣的:(Hadoop,3.2.1,源码)