Hadoop NameNode启动源码解析/nn介绍

Namenode介绍

Namenode如同一个管理者的角色
● 它负责维护整个hdfs文件系统的目录树结构以及文件所对应的block块信息(保存在磁盘上,因为文件与block块之间的关系不会发生变化);
● 它管理block块与datanode的映射关系(Namenode启动时候块上报,保存在内存中);
● 它能接收cilent的请求,接收datanode的块报告和心跳;
Namenode服务主要靠3个重要类实现
● Namenode类:启动类,管理配置等
● Namenode server:NameNodeRPCServer(接收和处理所有rpc请求)和NameNodeHttpServer(web)
● FSNamesystem:负责Namenode的所有逻辑,管理HDFS元数据

Namenode启动

启动流程图
在这里插入图片描述

启动源码解析

start-dfs.sh脚本是为了启动namenode,定位到Namenode启动类中main方法

public static void main(String argv[]) throws Exception {
    ...
    try {
      StringUtils.startupShutdownMessage(NameNode.class, argv, LOG);
      //创建NN对象
      NameNode namenode = createNameNode(argv, null);
      if (namenode != null) {
        //阻塞作用,让main线程无法结束
        namenode.join();
      }
    } ...
  }

createNameNode是重要的方法,用于构造NN对象

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

    boolean aborted = false;
    //根据启动选项执行不同的方法
    switch (startOpt) {
    case FORMAT:
      //初始化
      aborted = format(conf, startOpt.getForceFormat(),startOpt.getInteractiveFormat());
    case GENCLUSTERID:
    case ROLLBACK:
    case BOOTSTRAPSTANDBY:
    case INITIALIZESHAREDEDITS:
    case BACKUP:
    case CHECKPOINT:
    case RECOVER:
    case METADATAVERSION:
    case UPGRADEONLY:
    default:
      DefaultMetricsSystem.initialize("NameNode");
      //构造NN
      return new NameNode(conf);
    }
  }

配置NN后需要先初始化NN,进入format方法

private static boolean format(Configuration conf, boolean force,
      boolean isInteractive) throws IOException {
    ...
    FSImage fsImage = new FSImage(conf, nameDirsToFormat, editDirsToFormat);
    try {
      //构建FSNamesystem用于管理NN信息
      FSNamesystem fsn = new FSNamesystem(conf, fsImage);
      //初始化Journal
      fsImage.getEditLog().initJournalsForWrite();
    }
	...
    fsImage.format(fsn, clusterId, force);
  	...
    return false;
}

返回createNameNode,new NameNode(conf)构造NN对象

protected NameNode(Configuration conf, NamenodeRole role)
      throws IOException {
    //配置
    super(conf);
    // 根据配置确认是否开启了HA
    String namenodeId = HAUtil.getNameNodeId(conf, nsId);

    //是否启用ha
    this.haEnabled = HAUtil.isHAEnabled(conf, nsId);

    //非HA -> ACTIVE_STATE
    state = createHAState(getStartupOption(conf));
    //初始化
    initialize(getConf());
    //初始化完成后, Namenode进入Standby状态
    //在这里会开启StandbyCheckpointer里面的checkpointer 线程. 定时合并&处理images文件
    state.prepareToEnterState(haContext);
    state.enterState(haContext);
  }

initialize是重要方法,会初始化httpserver、rpserver,构造FSNamesystem用于管理文件系统
进入initialize(getConf())方法

protected void initialize(Configuration conf) throws IOException {
    //配置

    //构造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);

    //创建RPC服务/创建多个协议/创建serviceRpcServer和clientRpcServer
    rpcServer = createRpcServer(conf);

    //启动公共服务/资源检查/安全模式
    startCommonServices(conf);
    //启动计时器定期将NameNode度量写入日志文件。此行为可由配置禁用。
    startMetricsLogger(conf);
  }

重新回到initialize方法,进入startCommonServices(conf)

 /** Start the services common to active and standby states */
  private void startCommonServices(Configuration conf) throws IOException {
    //FSNameSystem是管理HDFS的元数据的。里面涉及关于元数据的东西
    namesystem.startCommonServices(conf, haContext);
    registerNNSMXBean();
    if (NamenodeRole.NAMENODE != role) {
      startHttpServer(conf);
      httpServer.setNameNodeAddress(getNameNodeAddress());
      httpServer.setFSImage(getFSImage());
    }
    //启动服务
    rpcServer.start();
    try {
      plugins = conf.getInstances(DFS_NAMENODE_PLUGINS_KEY,
          ServicePlugin.class);
    } catch (RuntimeException e) {
      String pluginsValue = conf.get(DFS_NAMENODE_PLUGINS_KEY);
      LOG.error("Unable to load NameNode plugins. Specified list of plugins: " +
          pluginsValue, e);
      throw e;
    }
    for (ServicePlugin p: plugins) {
      try {
        p.start(this);
      } catch (Throwable t) {
        LOG.warn("ServicePlugin " + p + " could not be started", t);
      }
    }
  }

进入namesystem.startCommonServices(conf, haContext)

void startCommonServices(Configuration conf, HAContext haContext) throws IOException {
    //NameNode资源检查 通过core-site.xml  hdfs-site.xml两个文件获取元数据存储位置
    //需要检查三个目录,因为这三个目录都涉及到了元数据
    //NameNode的两个目录:存储fsiamge的目录,存储editlog的目录。但是一般情况下,或者默认情况这两个使用的是同一个目录。
    //Journlanode里面有也有存储元数据的目录。高可用的模式了
    //获取到了要检查的目录
    nnResourceChecker = new NameNodeResourceChecker(conf);
    //检查是否有可用资源
    checkAvailableResources();
    //安全模式判断
    //获取正常的block块数
    long completeBlocksTotal = getCompleteBlocksTotal();
    prog.setTotal(Phase.SAFEMODE, STEP_AWAITING_REPORTED_BLOCKS,
          completeBlocksTotal);
    //启动重要服务
    blockManager.activate(conf, completeBlocksTotal);
  }

进入到blockManager.activate(conf, completeBlocksTotal)

public void activate(Configuration conf, long blockTotal) {
	//启动了等待复制的线程
    pendingReconstruction.start();
    //启动了管理 DataNode 的退役和维护状态线程和管理心跳的服务
    datanodeManager.activate(conf);
    this.redundancyThread.setName("RedundancyMonitor");
    this.redundancyThread.start();
    storageInfoDefragmenterThread.setName("StorageInfoMonitor");
    storageInfoDefragmenterThread.start();
    this.blockReportThread.start();
    mxBeanName = MBeans.register("NameNode", "BlockStats", this);
    bmSafeMode.activate(blockTotal);
  }

回到startCommonServices,启动httpServer和rpcServer
至此,Namenode启动成功

安全模式

在NN启动过程中,还可能进入安全模式
非正常启动
例如命令行参数为recover,直接进入安全模式
正常启动
● NN会去检查目录资源,如果发现资源不足(默认100M),进入安全模式

//由于资源不足,nameNodeHasResourcesAvailable为false
boolean nameNodeHasResourcesAvailable() {
    return hasResourcesAvailable;
}

//资源监控线程运行,检查资源,如果nameNodeHasResourcesAvailable为false,enterSafeMode(true)
class NameNodeResourceMonitor implements Runnable  {
    boolean shouldNNRmRun = true;
    @Override
    public void run () {
      try {
        while (fsRunning && shouldNNRmRun) {
          checkAvailableResources();
          if(!nameNodeHasResourcesAvailable()) {
            String lowResourcesMsg = "NameNode low on available disk space. ";
            if (!isInSafeMode()) {
              LOG.warn(lowResourcesMsg + "Entering safe mode.");
            } else {
              LOG.warn(lowResourcesMsg + "Already in safe mode.");
            }
            enterSafeMode(true);
          }
          try {
            Thread.sleep(resourceRecheckInterval);
          } catch (InterruptedException ie) {
            // Deliberately ignore
          }
        }
      } catch (Exception e) {
        FSNamesystem.LOG.error("Exception in NameNodeResourceMonitor: ", e);
      }
    }

    public void stopMonitor() {
      shouldNNRmRun = false;
    }
 }

● threshold != 0 && blockSafe < blockThreshold
block块上报 正常的块数目少于全部block数的阈值
● datanodeNum < datanodeThreshold
注册的DN数目小于设置的阈值数目,但由于datanodeThreshold默认设置为0,相当于没有启用

//进入bmSafeMode.activate(blockTotal),关于安全模式的判断
//当blockSafe >= blockThreshold && datanodeNum >= datanodeThreshold,areThresholdsMet才会为true
private boolean areThresholdsMet() {
    assert namesystem.hasWriteLock();
    // Calculating the number of live datanodes is time-consuming
    // in large clusters. Skip it when datanodeThreshold is zero.
    // We need to evaluate getNumLiveDataNodes only when
    // (blockSafe >= blockThreshold) is true and hence moving evaluation
    // of datanodeNum conditional to isBlockThresholdMet as well
    synchronized (this) {
      boolean isBlockThresholdMet = (blockSafe >= blockThreshold);
      boolean isDatanodeThresholdMet = true;
      if (isBlockThresholdMet && datanodeThreshold > 0) {
        int datanodeNum = blockManager.getDatanodeManager().
                getNumLiveDataNodes();
        isDatanodeThresholdMet = (datanodeNum >= datanodeThreshold);
      }
      return isBlockThresholdMet && isDatanodeThresholdMet;
    }
  }

void activate(long total) {
	//获取所有的block数
    setBlockTotal(total);
    //判断是否进入安全模式,如果areThresholdsMet为false,进入安全模式
    if (areThresholdsMet()) {
      boolean exitResult = leaveSafeMode(false);
      Preconditions.checkState(exitResult, "Failed to leave safe mode.");
    } else {
      // enter safe mode
      status = BMSafeModeStatus.PENDING_THRESHOLD;
      initializeReplQueuesIfNecessary();
      reportStatus("STATE* Safe mode ON.", true);
      lastStatusReport = monotonicNow();
    }
  }

进入areThresholdsMet(),当正常块大于等于阈值且DN数目大于等于DN数阈值时候,退出安全模式

private boolean areThresholdsMet() {
    synchronized (this) {
      boolean isBlockThresholdMet = (blockSafe >= blockThreshold);
      boolean isDatanodeThresholdMet = true;
      if (isBlockThresholdMet && datanodeThreshold > 0) {
        int datanodeNum = blockManager.getDatanodeManager().
                getNumLiveDataNodes();
        isDatanodeThresholdMet = (datanodeNum >= datanodeThreshold);
      }
      return isBlockThresholdMet && isDatanodeThresholdMet;
    }
  }

元数据存储

原理

NameNode维护了文件与数据块的映射表以及数据块与数据节点的映射表,真正的数据是存储在DataNode上。

计算机中存储数据有两种:内存或磁盘
内存效率高但是危险性大
考虑到两者的优缺点,HDFS采用内存+磁盘的形式来存储元数据(数据的属性)。
Namenode通过维护镜像文件fsimage和editlog操作日志文件将元数据存储在磁盘上。
Namenode启动的时候会去指定目录加载文件,存储在内存中,方便接收rpc请求快速做出响应。

fsimage和editlog是什么?

fsimage相当于一个快照(checkpoint)保存了最新的元数据检查点,在HDFS启动时加载fsimage的信息,包含了整个HDFS文件系统的所有目录和文件的信息,editlog主要是在NameNode已经启动情况下对HDFS进行的各种更新操作进行记录,HDFS客户端执行所有的写操作都会被记录到editlog中。

为什么需要editlog文件呢?

因为随着HDFS上的文件越来越大,fsimage也会越来越大(GB级别),将HDFS操作记录到fsimage会导致系统运行的十分缓慢,成本太大。而为了防止editlog文件越来越大,需要将editlog和fsimage合并。

fsimge和editlog合并过程概述

Standalone模式下,Secondary NameNode(以下简称SNN,Namenode简称NN)通过周期性(五分钟)以GetEditLog方法获取editlog的大小,当满足一定条件时候,NN停止使用editlog,并临时生成edit.new文件(为了防止数据写入到edit文件中),SNN通过http获取了NN的fsimage和editlog文件,载入fsimage,按照editlog的记录执行操作,合并完成后发送请求告诉NN,NN拉取SNN的新fsimage文件,将edit.new文件更改为edit文件。

为什么要通过其他的Namenode去合并fsimage?

Namenode启动后主要用于接收rpc请求作出响应,为了降低Namedode压力,在Standalone模式下通过Secondary NameNode,HA模式下通过Standby Namenode

如何保证内存中元数据和磁盘元数据的一致性呢?

Namenode启动时加载磁盘上的fsimage文件到内存中,即内存中存储了文件和块的关系,在接受Datanode块上报后内存中存储了块和DN的关系,启动后对HDFS的各种更新操作都记录到editlog文件中(会写到磁盘上),在达到一定条件后(默认1个小时或者editlog达到100w条操作)会进行合并为新的fsimage(Standalone模式下通过Secondary NameNode,HA模式下通过Standby Namenode)

存储格式

fsimage:文件和块的关系
● Path 目录路径
● Replication 备份数
● ModificationTime 最后修改时间
● AccessTime 最后访问时间
● PreferredBlockSize 首选块大小 byte
● BlocksCount 块 数
● FileSize 文件大小 byte
● NSQUOTA 名称配额 限制指定目录下允许的文件和目录的数量。
● DSQUOTA 空间配额 限制该目录下允许的字节数
● Permission 权限
● UserName 用户
● GroupName 用户组

blocksMap:块和数据节点的映射关系
主要功能是保存block和其元数据的映射,block的元数据为它所在的INode和存储该block的DataNode。Block对象记录了blockid、block大小以及时间戳信息。block->DataNode的信息没有持久化存储,而是NameNode通过DataNode的块上报获取该block所在的DataNode List。block的元数据对应的类为BlocksMap#BlockInfo。

QJM

基于QJM的共享存储系统主要用于保存editlog,采用多个JournalNode的节点组成的JournalNode集群来存储editlog。每个JN保存同样的editlog副本。每次NN写editlog的时候,除了向本地磁盘写入editlog之外,也会并行地向JN集群之中的每一个JN发送写请求,只要大多数的JN节点返回成功就认为向JN集群写入editlog成功。如果有2N+1台JN,根据大多数的原则,最多可以容忍有N台JN节点挂掉。

ANN向本地磁盘写入editlog的时候也会向JN集群写入,当JN集群过半节点返回成功就认为写入成功,JN集群中有自增epoch num用于标识每次ANN的生命周期(NN切换一次,epoch +1),SNN定时从JN集群同步editlog文件(会出现SNN的editlog落后于ANN的editlog),SNN转换成ANN的时候需要将落后的editlog同步。如果ANN出现宕机情况,JN集群节点之间的editlog不一致,此时需要恢复成一致(根据epoch更新成大的数字,拒绝小数字的操作,这样原有ANN写入操作会失败,进程退出,实现隔离),SNN同步JN集群的editlog,切换为ANN。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值