hdfs client分析:hdfs dfs -ls这一篇重点分析了hdfs client的整体执行流程,但是没有详细介绍命令调用和返回的过程,这篇通过mkdir命令来做个补充。

命令:hdfs dfs -mkdir

代码分析直接从Command类的run方法开始:

public int run(String...argv) {
      LinkedList<String> args = new LinkedList<String>(Arrays.asList(argv));
      try {
        if (isDeprecated()) {
          displayWarning(
                  "DEPRECATED: Please use '"+ getReplacementCommand() + "' instead.");
        }
        processOptions(args);
        processRawArguments(args);
      } catch (IOException e) {
        displayError(e);
      }
    
    return (numErrors == 0) ? exitCode : exitCodeForError();
  }

首先,是调用Mkdir.processOptions(args)

@Override
  protected void processOptions(LinkedList<String> args) {
    //将mkdir后面的参数解析为CommandFormat(本质是个map)
    CommandFormat cf = new CommandFormat(1, Integer.MAX_VALUE, "p");
    cf.parse(args);
    //是否有p参数(创建父目录)
    createParents = cf.getOpt("p");
  }

接下来,processRawArguments(args);

protected void processArguments(LinkedList<PathData> args)
  throws IOException {
    for (PathData arg : args) {
      try {
        processArgument(arg);
      } catch (IOException e) {
        displayError(e);
      }
    }
  }
  
  protected void processArgument(PathData item) throws IOException {
    if (item.exists) {
      processPathArgument(item);
    } else {
      processNonexistentPath(item);
    }
  }

最终执行的是Mkdir的processPath(PathData item)或processNonexistentPath(PathData item)

//直接抛错给用户提示信息
@Override
  protected void processPath(PathData item) throws IOException {
    if (item.stat.isDirectory()) {
      if (!createParents) {
        throw new PathExistsException(item.toString());
      }
    } else {
      throw new PathIsNotDirectoryException(item.toString());
    }

//真正创建目录
@Override
  protected void processNonexistentPath(PathData item) throws IOException {
    // check if parent exists. this is complicated because getParent(a/b/c/) returns a/b/c, but
    // we want a/b
    if (!item.fs.exists(new Path(item.path.toString()).getParent()) && !createParents) {
      throw new PathNotFoundException(item.toString());
    }
    if (!item.fs.mkdirs(item.path)) {
      throw new PathIOException(item.toString());
    }
  }

这个方法的功能是:
判断这个目录的父目录是否存在,如果不存在且不创建父目录,则抛出PathNotFoundException异常,即没有找到路径。
否则,通过方法item.fs.mkdirs()创建目录。此处的fs实例就是DistributedFileSystem类。

接下来看DistributedFileSystem类里的mkdirs()

@Override
  public boolean mkdirs(Path f, FsPermission permission) throws IOException {
    return mkdirsInternal(f, permission, true);
  }

  private boolean mkdirsInternal(Path f, final FsPermission permission,
      final boolean createParent) throws IOException {
    statistics.incrementWriteOps(1); // 更新统计数据,这个统计数据用于跟踪到目前为止在这个文件系统中有多少次读和写操作。这里统计的是写操作。
    Path absF = fixRelativePart(f);// 将相对路径转换成绝对路径
    // 创建一个`FileSystemLinkResolver`类的对象,并重写此类的`doCall`和`next`方法。
    // 然后调用对象的`resolve`方法。
    return new FileSystemLinkResolver<Boolean>() { 
      @Override
      public Boolean doCall(final Path p)
          throws IOException, UnresolvedLinkException {
        return dfs.mkdirs(getPathName(p), permission, createParent);
      }

      @Override
      public Boolean next(final FileSystem fs, final Path p)
          throws IOException {
        // FileSystem doesn't have a non-recursive mkdir() method
        // Best we can do is error out
        if (!createParent) {
          throw new IOException("FileSystem does not support non-recursive"
              + "mkdir");
        }
        return fs.mkdirs(p, permission);
      }
    }.resolve(this, absF);
  }

resolve方法的功能是:
1) 尝试使用指定的文件系统和路径,去执行doCall方法。
2) 如果doCall方法调用失败,则尝试重新解析路径,然后执行next方法。(解析出新的fs实例执行mkdir,如果新解析处理的fs跟原来的相同则不会执行next)

doCall方法
在doCall方法中,调用的是dfs.mkdirs(getPathName§, permission, createParent)方法,dfs是一个DFSClient类的对象(即连接NN的client)。

执行类DFSClient里的方法

public boolean mkdirs(String src, FsPermission permission,
      boolean createParent) throws IOException {
    if (permission == null) {
      permission = FsPermission.getDefault();
    }
    FsPermission masked = permission.applyUMask(dfsClientConf.uMask);
    return primitiveMkdir(src, masked, createParent);
  }

此方法实现的功能就是:使用给出的名字和权限,创建目录.

接下来,我们看看到底是怎么创建目录的,我们跟踪方法primitiveMkdir的内部实现,此方法的源码如下:

public boolean primitiveMkdir(String src, FsPermission absPermission, 
    boolean createParent)
    throws IOException {
    checkOpen();//检查客户端是否正在运行,即检查dfsClient是否运行,也相当于检查DistributedFileSystem(DFS)是否初始化,因此在DFS初始化的时候,会初始化dfsClient
    if (absPermission == null) {
      absPermission = 
        FsPermission.getDefault().applyUMask(dfsClientConf.uMask);
    } 

    if(LOG.isDebugEnabled()) {
      LOG.debug(src + ": masked=" + absPermission);
    }
    TraceScope scope = Trace.startSpan("mkdir", traceSampler);
    try {
      //创建目录
      return namenode.mkdirs(src, absPermission, createParent);
    } catch(RemoteException re) {
      throw re.unwrapRemoteException(AccessControlException.class,
                                     InvalidPathException.class,
                                     FileAlreadyExistsException.class,
                                     FileNotFoundException.class,
                                     ParentNotDirectoryException.class,
                                     SafeModeException.class,
                                     NSQuotaExceededException.class,
                                     DSQuotaExceededException.class,
                                     UnresolvedPathException.class,
                                     SnapshotAccessControlException.class);
    } finally {
      scope.close();
    }
  }

可以看到,实际上创建目录的任务是由namenode完成的,即通过向NN发送RPC请求完成的。