HDFS------hadoop namenode -format

本文深入解析了在HDFS集群搭建完成后,执行`/bin/hadoop namenode-format`命令时的具体流程,特别是如何通过构建FSNamesystem、FSImage以及格式化文件系统的过程,实现数据的初始化和存储结构的建立。

       集群搭建好了以后,通常我们会输入命令:/bin/hadoop namenode -format对hdfs进行格式化,那究竟格式化都做些什么具体的工作呢,怀着好奇心到源码里一探究竟。

首先从这行命令/bin/hadoop namenode -format 可以判断出会调用NameNode的main方法,到源码里一看,果然如此:

在org.apache.hadoop.hdfs.server.namenode包有个类NameNode,它的main方法如下:

---------------------------------------------------------------------------------------------------------------------------------

 public static void main(String argv[]) throws Exception {
    try {
      StringUtils.startupShutdownMessage(NameNode.class, argv, LOG);
      NameNode namenode = createNameNode(argv, null);
      if (namenode != null)
        namenode.join();
    } catch (Throwable e) {
      LOG.error(StringUtils.stringifyException(e));
      System.exit(-1);
    }
  }

---------------------------------------------------------------------------------------------------------------------------------

    其中  NameNode namenode = createNameNode(argv, null);用来创建namenode,进入方法中:

public static NameNode createNameNode(String argv[],
                                 Configuration conf) throws IOException {
    if (conf == null)
      conf = new Configuration();
    StartupOption startOpt = parseArguments(argv);
    if (startOpt == null) {
      printUsage();
      return null;
    }
    setStartupOption(conf, startOpt);

    switch (startOpt) {
      case FORMAT:
        boolean aborted = format(conf, true);
        System.exit(aborted ? 1 : 0);

      case FINALIZE:
        aborted = finalize(conf, true);
        System.exit(aborted ? 1 : 0);
      default:
    }
    DefaultMetricsSystem.initialize("NameNode");
    NameNode namenode = new NameNode(conf);
    return namenode;
  }

其中FORMAT即为我们在命令行输入的-format,此时会调用format方法,下面集中看这个方法做了哪些工作:

===========================================================================================

private static boolean format(Configuration conf,
                                boolean isConfirmationNeeded
                                ) throws IOException {

/******dirsToFormat是我们在配置文件中dfs.name.dir的value,这个value默认是在/tmp/hadoop/dfs/name目录下******/
    Collection<File> dirsToFormat = FSNamesystem.getNamespaceDirs(conf);
    Collection<File> editDirsToFormat =
                 FSNamesystem.getNamespaceEditsDirs(conf);
    for(Iterator<File> it = dirsToFormat.iterator(); it.hasNext();) {
      File curDir = it.next();
      if (!curDir.exists())
        continue;
      if (isConfirmationNeeded) {
        System.err.print("Re-format filesystem in " + curDir +" ? (Y or N) ");
        if (!(System.in.read() == 'Y')) {
          System.err.println("Format aborted in "+ curDir);
          return true;
        }
        while(System.in.read() != '\n'); // discard the enter-key
      }
    }

/*******构建一个FSNamesystem,构建一个FSImage,然后把FSImage传给了FSNamesystem,其它的先不要管,我们先来看下

*******FSImage的构建。
    FSNamesystem nsys = new FSNamesystem(new FSImage(dirsToFormat,
                                         editDirsToFormat), conf);
    nsys.dir.fsImage.format();
    return false;
  }   
===========================================================================================

FSImage里做的工作很简单就是绑定dirsToFormat(默认为/tmp/hadoop/dfs/name)和editDirsToFormat(默认为/tmp/hadoop/dfs/name)

在FSImage的构造函数里会调用setStorageDirectories方法,它的作用是将dfs.name.dir和dfs.name.edits.dir这两个变量所设置的目录去重,然后将其目录以及目录的类型NameNodeDirType设定到FSImage中去。

回到nsys.dir.fsImage.format();这行代码其实调用的就是我们刚刚创建的FSImage,它的format方法。format的主要工作也是由这个方法来完成的。

====================================================================================================================

  public void format() throws IOException {
    this.layoutVersion = FSConstants.LAYOUT_VERSION;
    this.namespaceID = newNamespaceID();
    this.cTime = 0L;
    this.checkpointTime = FSNamesystem.now();
    for (Iterator<StorageDirectory> it =
                           dirIterator(); it.hasNext();) {
      StorageDirectory sd = it.next();
      format(sd);
    }
  }

======================================================================================================================

format方法里首先把设置一些fsimage的变量,例如:layoutVersion,namespaceID,time,checkpointtime等,然后遍历storageDirs,依次进行format

  /** Create new dfs name directory.  Caution: this destroys all files
   * in this filesystem. */
  void format(StorageDirectory sd) throws IOException {
    sd.clearDirectory(); // create currrent dir
    sd.lock();
    try {
      saveCurrent(sd);
    } finally {
      sd.unlock();
    }
    LOG.info("Storage directory " + sd.getRoot()
             + " has been successfully formatted.");
  }

可以看到,首先将current目录删除(如果有的话),然后调用 saveCurrent(sd);

  protected void saveCurrent(StorageDirectory sd) throws IOException {
    File curDir = sd.getCurrentDir();
    NameNodeDirType dirType = (NameNodeDirType)sd.getStorageDirType();
    // save new image or new edits
    if (!curDir.exists() && !curDir.mkdir())
      throw new IOException("Cannot create directory " + curDir);
    if (dirType.isOfType(NameNodeDirType.IMAGE))
      saveFSImage(getImageFile(sd, NameNodeFile.IMAGE));
    if (dirType.isOfType(NameNodeDirType.EDITS))
      editLog.createEditLogFile(getImageFile(sd, NameNodeFile.EDITS));
    // write version and time files
    sd.write();
  }


saveCurrent方法中首先获取这个storageDir的current目录,如果它的current不存在则先创建current,然后根据storageDir的type,来决定创建fsimage文件或者使edits文件。最后是创建version文件和time文件。

下面一张截图是我从自己的测试机上截取下来的,可以看到current目录下面的四个文件:


======================================================================================

那这四个文件里面具体都写的是哪些数据呢,再此怀着好奇心,去看看到底写的什么,先看fsimage这个文件,

void saveFSImage(File newFile) throws IOException {
    FSNamesystem fsNamesys = FSNamesystem.getFSNamesystem();
    FSDirectory fsDir = fsNamesys.dir;
    long startTime = FSNamesystem.now();
    //
    // Write out data
    //
    DataOutputStream out = new DataOutputStream(
                                                new BufferedOutputStream(
                                                                         new FileOutputStream(newFile)));
    try {
      out.writeInt(FSConstants.LAYOUT_VERSION);                   =============写入布局版本id

      out.writeInt(namespaceID);                                                   =============写入命名空间id

      out.writeLong(fsDir.rootDir.numItemsInTree());                     =============写入根目录(这里指的是dfs.name.dir)
      out.writeLong(fsNamesys.getGenerationStamp());               =============写入创建文件系统时间戳
      byte[] byteStore = new byte[4*FSConstants.MAX_PATH_LENGTH];
      ByteBuffer strbuf = ByteBuffer.wrap(byteStore);
      // save the root
      saveINode2Image(strbuf, fsDir.rootDir, out);                         =============写入文件或者目录的元数据信息,包括复制级别,修改时间,访问时间,块信息等
      // save the rest of the nodes
      saveImage(strbuf, 0, fsDir.rootDir, out);
      fsNamesys.saveFilesUnderConstruction(out);
      fsNamesys.saveSecretManagerState(out);
      strbuf = null;
    } finally {
      out.close();
    }

    LOG.info("Image file of size " + newFile.length() + " saved in "
        + (FSNamesystem.now() - startTime)/1000 + " seconds.");
  }

=========================================================

可以看到,采用了递归调用的方式(saveINode2Image,saveImage)将root目录树结构存储了起来。

最后来看下saveINode2Image这个方法:

private static void saveINode2Image(ByteBuffer name,
                                      INode node,
                                      DataOutputStream out) throws IOException {
    int nameLen = name.position();
    out.writeShort(nameLen);
    out.write(name.array(), name.arrayOffset(), nameLen);
    if (!node.isDirectory()) {  // write file inode
      INodeFile fileINode = (INodeFile)node;
      out.writeShort(fileINode.getReplication());
      out.writeLong(fileINode.getModificationTime());
      out.writeLong(fileINode.getAccessTime());
      out.writeLong(fileINode.getPreferredBlockSize());
      Block[] blocks = fileINode.getBlocks();
      out.writeInt(blocks.length);
      for (Block blk : blocks)
        blk.write(out);
      FILE_PERM.fromShort(fileINode.getFsPermissionShort());
      PermissionStatus.write(out, fileINode.getUserName(),
                             fileINode.getGroupName(),
                             FILE_PERM);
    } else {   // write directory inode
      out.writeShort(0);  // replication
      out.writeLong(node.getModificationTime());
      out.writeLong(0);   // access time
      out.writeLong(0);   // preferred block size
      out.writeInt(-1);    // # of blocks
      out.writeLong(node.getNsQuota());
      out.writeLong(node.getDsQuota());
      FILE_PERM.fromShort(node.getFsPermissionShort());
      PermissionStatus.write(out, node.getUserName(),
                             node.getGroupName(),
                             FILE_PERM);
    }
  }

这个方法很明显,分为文件和目录两个部分,如果使文件的话,记录的信息包括文件的块大小,一个文件包括哪些块(块id,块的数量等),如果是目录,大多属性都设置为0








### 3.1 NameNode格式化操作详解 在Hadoop中,NameNode的格式化操作是初始化HDFS文件系统元数据的关键步骤。执行 `hdfs namenode -format` 命令时,系统会根据配置文件中的参数生成新的文件系统命名空间信息,并创建必要的元数据存储目录[^2]。 #### 3.2 格式化命令 执行NameNode格式化的基本命令如下: ```bash hdfs namenode -format ``` 如果需要手动指定集群ID(clusterId),可以使用以下命令: ```bash hdfs namenode -format -clusterId <cid> ``` 此外,还可以添加 `-force` 或 `-nonInteractive` 参数来跳过确认提示或强制格式化操作。例如: ```bash hdfs namenode -format -clusterId mycluster -force ``` #### 3.3 格式化流程分析 在执行格式化命令时,NameNode会按照以下流程进行初始化操作: - **获取配置信息**:NameNode会从 `hdfs-site.xml` 中读取 `nameserviceId` 和 `namenodeId` 等配置项,用于确定当前集群的逻辑名称服务和节点角色。 - **检查格式化权限**:通过配置项 `dfs.namenode.support.allow.format` 判断是否允许格式化操作。在生产环境中,建议将此值设为 `false`,以防止误操作导致数据丢失。 - **确定存储目录**:NameNode会获取 `fsImage` 和 `edits` 的本地存储目录,以及 `sharedEditsDirs`(用于HA模式下的共享编辑日志目录)。 - **生成ClusterID**:如果启动参数中未指定 `clusterId`,系统会自动生成一个随机的ClusterID(格式为 `CID-UUID`)。 - **创建FsImage对象和FSNamesystem**:NameNode会创建 `FsImage` 对象和 `FSNamesystem` 实例,准备进行格式化操作。 - **检查现有数据**:如果处于HA模式,NameNode会连接JournalNode,检查目标目录是否已有数据,避免重复格式化。 - **生成NamespaceInfo**:调用 `NNStorage.newNamespaceInfo()` 方法生成新的命名空间信息,包括 `namespaceID`、`clusterID`、`cTime`、`storageType`、`layoutVersion` 和 `blockPoolID` 等关键信息。 - **创建current目录并写入VERSION文件**:NameNode会在本地和共享目录中创建 `current` 文件夹,并写入 `VERSION` 文件和 `seen_txid` 文件。 - **格式化JournalNode**:NameNode会连接JournalNode,并执行格式化操作,创建 `${nameserviceId}/current` 和 `${nameserviceId}/edits.sync` 等目录结构,其中包含 `VERSION`、`paxos`、`committed-txid` 等文件。 - **保存FsImage**:最后一步是调用 `saveFSImageInAllDirs()` 方法,将 `FSNamesystem` 保存到所有配置的 `fsImage` 目录中,生成 `fsimage` 和 `.md5` 校验文件,此时事务ID(txid)为0,表示格式化完成[^2]。 #### 3.4 常见问题与注意事项 - **配置文件错误**:格式化失败的一个常见原因是 `hdfs-site.xml` 配置错误,例如 `dfs.namenode.shared.edits.dir` 地址配置错误。若配置的JournalNode地址错误(如将 `slave` 错误地写入),会导致无法连接到JournalNode,从而导致格式化失败。应确保配置的地址正确,例如将 `slave` 改为实际的主机名 `dem02`[^1]。 - **权限问题**:格式化时可能会出现目录创建失败的错误,如 `Cannot create directory /opt/data-hadoop/namenode/current`,这通常是因为运行Hadoop的用户没有对目标目录的写权限。应确保配置的 `dfs.namenode.name.dir` 路径存在且可写。 - **重复格式化**:在生产环境中,不建议频繁格式化NameNode,因为这会清除所有元数据。如果确实需要重新格式化,应确保集群处于安全状态,并使用 `-force` 参数强制执行。 ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值