NameNode 初始化过程

HDFS文件系统是主从分布式文件系统,其中其中主节点为master,上面有一个非常重要的节点NameNode ,它的作用有管理整个集群的元数据信息、响应客户端的请求、接受DataNode心跳信息、定期CheckPoint等,它管理着集群。在安装hadoop之后启动之前先需要进行格式化操作,然后会用sbin/hadoop-dasmon.sh start namenode启动进程。 在源码中可以看到在NameNode类中的main方法去启动一个NameNode

/**
   */
  public static void main(String argv[]) throws Exception {
    if (DFSUtil.parseHelpArgument(argv, NameNode.USAGE, System.out, true)) {
      System.exit(0);
    }

    try {
      // Print a log message for starting up and shutting down
      /**
       * TODO 打印一些信息在启动和关闭的是偶
       */
      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);
    }
  }

createNameNode方法将解析启动脚本中的参数,默认的就是利用构造函数实例化一个NameNode

default: {
        DefaultMetricsSystem.initialize("NameNode");
        return new NameNode(conf);
      }

在构造函数中:

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);
    // 关于是否是 HA 高可用状态
    state = createHAState(getStartupOption(conf));
    this.allowStaleStandbyReads = HAUtil.shouldAllowStandbyReads(conf);
    this.haContext = createHAContext();
    try {
      initializeGenericKeys(conf, nsId, namenodeId);
      /**
       * Initialize name-node.
       */
      initialize(conf);
      try {
      	// 通过ReadWriteLock读写锁和ReentrantLock可重入锁,防止线程问题
        haContext.writeLock();
        // 绑定ha上下文信息
        state.prepareToEnterState(haContext);
        // TODO Active 和 StandBy NameNode 会调用不同的方法
        /**
         * org.apache.hadoop.hdfs.server.namenode.ha.ActiveState#enterState
         *    -- Start services required in active state
         * org.apache.hadoop.hdfs.server.namenode.ha.StandbyState#enterState
         *    -- Start services required in standby state
         */
        state.enterState(haContext);
      } finally {
      	// 释放锁
        haContext.writeUnlock();
      }
    } catch (IOException e) {
    
	  /**
	   * Stop all NameNode threads and wait for all to finish.
	   */
      this.stop();
      throw e;
    } catch (HadoopIllegalArgumentException e) {
    	
	  /**
	   * Stop all NameNode threads and wait for all to finish.
	   */
      this.stop();
      throw e;
    }
    this.started.set(true);
  }

在initialize方法中会:
1.启动一个http服务器,启动后通过50070端口访问hdfs的管理页面,
2.核心代码:FSNamesystem初始化【 FSNamesystem.loadFromDisk(conf)】
   a.checkConfiguration(conf); 从过conf文件校验配置文件信息
   b.根据conf文件实例FSImage,并调用FSImage.recoverTransitionRead,返回boolean staleImage
      利用FSImage 分析存储目录,看是否需要从前一个状态中恢复?是否根据命名空间信息执行fs状态转换?如果需要的话则将image与EditLogs合并,(有点像checkpoint?),过程中会:
如果不是是HA 那么调用FSEditLog.initJournalsForWrite 和 FSEditLog.recoverUnclosedStreams
如果是HA,那么调用FSEditLog.initSharedJournalsForRead
   c.boolean needToSave = staleImage && !haEnabled && !isRollingUpgrade()
判断当前images是否需要保存、是否不是高可用、是否非滚动升级中
   d.needToSave 为true时,则将FSimage 保存起来在当前每一个存储目录中
   e.之后如果时非HA则会创建一个新的日志segment 并写入seen_txid file,hadoop时利用双缓冲队列进行日志的高速读写,

if (!haEnabled || (haEnabled && startOpt == StartupOption.UPGRADE)
          || (haEnabled && startOpt == StartupOption.UPGRADEONLY)) {
        fsImage.openEditLogForWrite();
      }

   f.NameNodeMetrics(收集各种指标参数),将本次loadFSImage的过程耗时放入到指标系统中
3.核心代码:创建一个rpc server实例
   hadoop内部通过这个RPC实现进程之间的交互,其中在实例化的时候利用了构造者模式,例如:

// 提供给DataNodes节点的server,监听DataNodes的请求,这个server会绑定很多协议,提供不同的功能
this.serviceRpcServer = new RPC.Builder(conf)
          .setProtocol(
              org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolPB.class)
          .setInstance(clientNNPbService)
          .setBindAddress(bindHost)
          .setPort(serviceRpcAddr.getPort()).setNumHandlers(serviceHandlerCount)
          .setVerbose(false)
          .setSecretManager(namesystem.getDelegationTokenSecretManager())
          .build();
// 提供给客户端的server,监听clients的请求,这个server会绑定很多协议,提供不同的功能
 this.clientRpcServer = new RPC.Builder(conf)
        .setProtocol(
            org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolPB.class)
        .setInstance(clientNNPbService).setBindAddress(bindHost)
        .setPort(rpcAddr.getPort()).setNumHandlers(handlerCount)
        .setVerbose(false)
        .setSecretManager(namesystem.getDelegationTokenSecretManager()).build();

4.核心代码:启动一些服务组件,包括rpc server等
   启动一些公共的服务激活为active或standby stats,这个过程中调用org.apache.hadoop.hdfs.server.namenode.FSNamesystem#startCommonServices方法,在其中:
   a.创建一个NameNodeResourceChecker,获取了要检查的目录(这里面会检查三个目录,分别是存储fsimage目录、存储editlog的目录、JournalNode的存储目录,前面2个是涉及NameNode的元数据存储【一般这2个默认是一个目录】,后面一个是HA模式下同步元数据有关,在org.apache.hadoop.hdfs.server.namenode.FSNamesystem#getNamespaceEditsDirs(org.apache.hadoop.conf.Configuration, boolean) 方法中,

// 默认情况下这个2个目录是同一个目录
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) {
      // TODO JournalNode 的目录
      // 通过参数 DFS_NAMENODE_SHARED_EDITS_DIR_KEY = "dfs.namenode.shared.edits.dir";
      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.
    //TODO NameNode 的相关存储目录
    // 参数:DFS_NAMENODE_EDITS_DIR_KEY = "dfs.namenode.edits.dir";
    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);
    }
  }

检查namenode所在节点的机器上是否有足够的磁盘空间。如果资源不够则输出警告日志,并进入安全模式。
   b.在setBlockTotal()方法中,会先获取集群可用的Block块个数【总的Blocks数 - 正在构建的Block数 = 集群可以使用的Block】,(PS:Block会有COMPLETE和UnderConstruction2种不同状态,COMPLETE表示是一个可以使用的完整的block,UnderConstruction表示正在构建的状态的bloak 还不能使用)。
然后看是否满足集群启动的阈值(DFS_NAMENODE_SAFEMODE_THRESHOLD_PCT_DEFAULT = 0.999f 默认值),进行安全模式的校验:
      1.threshold != 0 && blockSafe < blockThreshold):
      blockSafe namenode在datanode启动后 接受的block数之和、blockThreshold :安全模式的阈值,即:上报的Block数<集群的阈值 则进入安全模式
      2.(datanodeThreshold != 0 && getNumLiveDataNodes() < datanodeThreshold)
      datanodeThreshold: datanode的阈值 小于此值则进入 安全模式,默认值 :DFS_NAMENODE_SAFEMODE_MIN_DATANODES_DEFAULT = 0;
      3.(!nameNodeHasResourcesAvailable())
      这方法是校验是否有足够的磁盘空间,这个空间有一个默认参数,DFS_NAMENODE_DU_RESERVED_DEFAULT = 1024 * 1024 * 100; // 100 MB ,所以最少需要100M的磁盘空间,否则进入安全模式 ,这个是通过NameNodeResourceChecker 这个类实现的

      上面的条件之间是或者关系,即有一个满足则进入安全模式!

      4.org.apache.hadoop.hdfs.server.blockmanagement.BlockManager#activate(),在FSNamesystem中有几个重要的组件,其中一个就是BlockManager,负责管理Block块的信息,它的里面有一个HeartbeatManager,因为是负责管理分布在各个DataNode上的Block块信息的,所以其内部会有一个线程定期(默认是5s)的去接受DataNode发送的心跳信息,这个线程就是在HeartbeatManager中的heartbeatThread,只要namesystem.isRunning()在运行那么就会定期进行心跳检查。

/************************************heartbeatThread的定期心跳检查部分**********************************************/
@Override
    public void run() {
      while(namesystem.isRunning()) {
        try {
          final long now = Time.monotonicNow();
          if (lastHeartbeatCheck + heartbeatRecheckInterval < now) {
            heartbeatCheck();
            lastHeartbeatCheck = now;
          }
          if (blockManager.shouldUpdateBlockKey(now - lastBlockKeyUpdate)) {
            synchronized(HeartbeatManager.this) {
              for(DatanodeDescriptor d : datanodes) {
                d.needKeyUpdate = true;
              }
            }
            lastBlockKeyUpdate = now;
          }
        } catch (Exception e) {
          LOG.error("Exception while checking heartbeat", e);
        }
        try {
          Thread.sleep(5000);  // 5 seconds
        } catch (InterruptedException ie) {
        }
      }
    }
/*********************************************************************************/


/************************************************************/
// org.apache.hadoop.hdfs.server.namenode.FSNamesystem#startCommonServices
  /** 
   * Start services common to both active and standby states
   */
  void startCommonServices(Configuration conf, HAContext haContext) throws IOException {
    this.registerMBean(); // register the MBean for the FSNamesystemState
    writeLock();
    this.haContext = haContext;
    try {
      /**
       * // 创建了一个NameNodeResourceChecker对象,获取了要检查的目录
       * 用来检查namenode所在机器上的磁盘空间是否足够
       */
      nnResourceChecker = new NameNodeResourceChecker(conf);
      /**
       *  // 检查可用资源是否足够:如果不够,日志打印警告信息,然后进入安全模式
       */
      checkAvailableResources();
      /**
       * assert [boolean 表达式] 断言
       *    如果[boolean表达式]为true,则程序继续执行。
       *    如果为false,则程序抛出AssertionError,并终止执行。
       *
       *
       */
      assert safeMode != null && !isPopulatingReplQueues();
      StartupProgress prog = NameNode.getStartupProgress();
      prog.beginPhase(Phase.SAFEMODE);
      prog.setTotal(Phase.SAFEMODE, STEP_AWAITING_REPORTED_BLOCKS,
        getCompleteBlocksTotal());
      /**
       * HDFS 的安全模式的检查(****面试题******)
       *
       */
      setBlockTotal();

      /**
       *  启动重要服务
       */
      blockManager.activate(conf);
    } finally {
      writeUnlock();
    }
    
    registerMXBean();
    DefaultMetricsSystem.instance().register(this);
    if (inodeAttributeProvider != null) {
      inodeAttributeProvider.start();
      dir.setINodeAttributeProvider(inodeAttributeProvider);
    }
    snapshotManager.registerMXBean();
  }
  

下面是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);

    if (NamenodeRole.NAMENODE == role) {
      /**
       * //启动http服务器,启动后可以通过http://namenode:50070 访问hdfs的管理页面
       */
      startHttpServer(conf);
    }

    this.spanReceiverHost = SpanReceiverHost.getInstance(conf);

    /**
     * // 核心代码:FSNamesystem初始化
     */
    loadNamesystem(conf);

    /**
     * // 核心代码:创建一个rpc server实例
     */
    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);

    /**
     * // 核心代码:启动一些服务组件,包括rpc server等
     */
    startCommonServices(conf);
  }
  

总结一下这个过程中涉及的重要的类以及关键部分:


NameNode:
	main(): 启动NameNode
		createNameNode:读取配置文件,创建一个namenode
		DefaultMetricsSystem.initialize(): 初始化默认的指标系统
		NameNode(conf) :调用NameNode构造方法
			initialize(conf):初始化NameNode以及相关的组件
				startHttpServer(conf): 启动NameNodeHttpServer 提供访问页面
				loadNamesystem(conf):FSNamesystem初始化
				createRpcServer(conf):创建一个NameNodeRpcServer【其中RPC.Server serviceRpcServer(为DataNodes 服务),RPC.Server clientRpcServer(为客户端服务)】
				FSNamesystem.startCommonServices(conf)
					1.  创建一个NameNodeResourceChecker对象,获取了要检查的目录用来检查namenode所在机器上的磁盘空间是否足够
					2. 可用资源是否足够:如果不够,日志打印警告信息
					3. FSNamesystem.setBlockTotal():的安全模式的检查(****面试题******4. BlockManager.activate(conf):
						4.1 PendingReplicationBlocks.start() :启动了等待复制的线程
						4.2 datanodeManager.activate(conf) :启动了管理心跳的服务
								4.2.1  启动了 管理下线datanode的服务 DecommissionManager.activate(conf)
								4.2.2  启动了管理心跳的线程HeartbeatManager.activate(conf);
						4.3 replicationThread.start()  : block 副本复制相关线程
		

附上简图,
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值