一、概述

二、实现

一直点点点~~~,最终代码定位到 NameNodeRpcServer#mkdirs()

1
2
// 此处调用服务端 RPC
boolean mkdirs = namenode.mkdirs(src, absPermission, createParent);

检查 NameNode 是否启动

1
checkNNStartup();

namesystem.mkdirs

1
return namesystem.mkdirs(src, new PermissionStatus(getRemoteUser().getShortUserName(), null, masked), createParent);
  • 检查权限

    1
    checkOperation(OperationCategory.WRITE);
  • 检查是否是安全模式

    判断是否是安全模式,如果是安全模式是不可以写入数据的

    1
    checkNameNodeSafeMode("Cannot create directory " + src);
  • 创建文件夹

    1
    auditStat = FSDirMkdirOp.mkdirs(this, src, permissions, createParent);
1
existing = createChildrenDirectories(fsd, existing, ancestors, addImplicitUwx(permissions, permissions));

2.1. 解析路径,得到 INode

2.1.1. 校验是否是有效路径(以/为开头)

1
2
3
if (!DFSUtil.isValidName(src)) {
throw new InvalidPathException(src);
}

2.1.2. 得到处理后的 src 路径

1
src = fsd.resolvePath(pc, src, pathComponents);

/usr/hive/warehose/data

2.1.3. 将当前路径解析成 INode 结构路径

1
INodesInPath iip = fsd.getINodesInPath4Write(src);

2.1.4. 获取上一个 INode

1
final INode lastINode = iip.getLastINode();

现有路径:/user/hive/warehouse
需要创建:/user/hive/warehouse/data
上一个 INode 就是:warehouse
如果之前的节点是不存在的 /user/hive/warehouse ,那么 lastINode = /

2.1.5. 返回当前传入的 INode 结构

1
INodesInPath existing = lastINode != null ? iip : iip.getExistingINodes();

2.1.6. 获取不存在的节点路径

1
List<String> nonExisting = iip.getPath(existing.length(), iip.length() - existing.length());

2.2. 添加 INode

1
2
existing = unprotectedMkdir(fsd, fsd.allocateNewInodeId(), existing,
localName.getBytes(Charsets.UTF_8), perm, null, now());

二、更新文件目录树

更新文件目录树,这颗目录树是存在于内存中,由 FSDirectory 管理

1
2
existing = unprotectedMkdir(fsd, fsd.allocateNewInodeId(), existing,
localName.getBytes(Charsets.UTF_8), perm, null, now());

可能在其他地方也会出现,这个unprotected 一般指的就是内存

2.1. 构建新增节点

如果是多重目录,则构建第一个不存在的节点

1
final INodeDirectory dir = new INodeDirectory(inodeId, name, permission, timestamp);

2.2. 添加节点到父目录

构建的节点添加到父目录下面,构成树结构

1
INodesInPath iip = fsd.addLastINode(parent, dir, true);

2.2.1. 获取父目录

1
final INodeDirectory parent = existing.getINode(pos - 1).asDirectory();

2.2.2. 在父目录下添加解析后的 INode

在老版本的 HDFS 代码中,向 INodeDirectory 中添加、删除以及修改子目录项的操作只需要更改 INodeDirectory.children 字段即可。添加了快照机制之后,对于子目录项的修改都需要由 DirectoryWithSnapshotFeature 代理。

DirectoryWithSnapshotFeature 类用于存储和处理指定目录下建立的所有快照,也就是记录目录快照之间进行的添加、删除、修改目录项的操作。

DirectoryDiff、DireetoryDiffList 以及 ChildrenDiff 类都是 Direetory WithSnapshotFeature 类的内部类。

截屏2022-08-13 16.48.17
1
added = parent.addChild(inode, true, existing.getLatestSnapshotId());
截屏2022-08-13 18.04.27
  1. 判断添加的子目录项是否己经在当前的 INodeDirectory

    addChild()方法首先判断添加的子目录项是否己经在当前的 INodeDirectory 中

    1
    2
    3
    4
    final int low = searchChildren(node.getLocalNameBytes());
    if (low >= 0) {
    return false;
    }
  2. 判断当前 INodeDirectory 对象是否在最新的快照中

    然后判断当前 INodeDirectory 对象是否在最新的快照中,如果是则获取 DireetoryWithSnapshotFeature 对象,并给 INodeDirectory 添加 DirectoryWithSnapshotFeature 特性,之后 INodeDirectory 中所有对于 children 字段的修改方法都会由 DirectoryWithSnapshotFeature 中对应的方法代理,比如 addChild()、removeChild()方法等。

    如果不在最新的快照中,则直接调用 INodeDirectory 的 addChild() 方法,将 INode 节点加入 INodeDirecotry的 children 集合中。

    1
    2
    3
    4
    5
    6
    7
    8
    if (isInLatestSnapshot(latestSnapshotId)) {
    DirectoryWithSnapshotFeature sf = this.getDirectoryWithSnapshotFeature();
    if (sf == null) {
    sf = this.addSnapshotFeature(null);
    }
    return sf.addChild(this, node, setModTime, latestSnapshotId);
    }
    addChild(node, low);

    获取 DireetoryWithSnapshotFeature 对象,然后在这个对象上调用 adaChild()方法,DirectoryWithSnapshotFeature 的 addChild() 方法首先从 DirectoryDiffList 中获取当前快照对应的 DirectoryDiff 对象,这个对象用于保存快照建立之后对目录的添加、删除、修改子目录项等操作。

    • 获取 ChildrenDiff对象

      DirectoryDiff 对象会将当前目录中新创建的子目录项放入 DirectoryDiff.childrenDiff 字段的 c-list 中,而将删除的子目录项放入DirectoryDiff.childrenDiff 字段的 d-list 中

      1
      ChildrenDiff diff = diffs.checkAndAddLatestSnapshotDiff(latestSnapshotId,parent).diff;
    • 添加 INode 到 ChildrenDiff 的 c-list

      之后 ChildrenDiff.create()方法将这个新添加的 INode 对象放入 ChildrenDiff 的 c-list中。成功在 DirectoryDiff 对象中记录修改操作后,DirectoryWithSnapshotFeature 的代理工作就完成了

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      private void insert(final ListType type, final E element, final int i) {
      List<E> list = type == ListType.CREATED? created: deleted;
      if (i >= 0) {
      throw new AssertionError("Element already exists: element=" + element
      + ", " + type + "=" + list);
      }
      if (list == null) {
      list = new ArrayList<E>(DEFAULT_ARRAY_INITIAL_CAPACITY);
      if (type == ListType.CREATED) {
      created = list;
      } else if (type == ListType.DELETED){
      deleted = list;
      }
      }
      list.add(-i - 1, element);
      }
    • INode 对象放入INodeDirectory. children 字段

      最后 addChild()方法会调用 INodeDirectory.add()方法,将新创建的 INode 对象放入INodeDirectory. children 字段中。

  3. 更新父目录的修改时间

    1
    2
    3
    if (setModTime) {
    updateModificationTime(node.getModificationTime(), latestSnapshotId);
    }

到这儿 Namenode 内存中文件系统目录树更新就完成了~~~