美文网首页
HDFS 文件系统

HDFS 文件系统

作者: wayyyy | 来源:发表于2022-12-10 22:03 被阅读0次

    在HDFS中,不管是目录还是文件,在文件系统目录树中都被看作是一个 INode 节点。
    如果是文件,则其对应的类为INodeFile
    如果是目录,则其对应的类为 INodeDirectoryINodeDirectory包含一个成员变量children,如果该目录下有子目录或者文件,其子目录或文件的引用就会被保存在集合中,HDFS就是通过这种方式来维护整个文件系统的目录结构。

    INodeFileINodeDirectory 都是 INode 派生类。 HDFS 中 INode 继承关系如图:

    INode 抽象类

    INode 是一个抽象类,它实现了 INodeAttributes 接口,这个接口包括了下面元信息的 get 方法:

    • userName 文件/目录所属用户名
    • groupName 文件/目录所属组名
    • fsPermission 文件/目录访问权限
    • aclFeature 安全相关
    • modificationTime 文件/目录上次修改时间
    • accessTime 文件/目录上次访问时间
    • XAttrFeature 当前文件/目录的扩展属性

    INode 抽象类除了实现接口外,还定义了INode 元信息的 get 和 set 方法,包括:

    • id INode 的 id
    • name 文件/目录的名称
    • fullparentpath 文件/目录的完整路径
    • parent 文件/目录的父节点
      同时INode 还提供了如下几个基本的判断方法:
    • isFile()
    • isDirectory()
    • isSymlink()
    • isRoot()

    INode 类中只有1个字段,就是 parent,表明当前 INode 的父目录。除了根目录外,其他所有文件和目录都存在一个父目录。

    INodeWithAdditionalFields 类

    INode 类中只有1个字段,就是parent,而其他元信息包括:id name permission modificationTime accessTime等字段,定义在INodeWithAdditionalFields,同时还覆盖了 INode 中对应的抽象方法。

    • permission 字段
      其中,重点讲讲 permission 字段。permission 字段是 long 类型,其中前16个比特用来存放文件模式标识,中间25个比特用来存放用户组标识,最后23个是比特用来存放用户名标识,其中``类就是来解析以及处理工具类。
    INodeDirectory 类

    INodeDirectory 抽象了HDFS 文件系统中的目录,目录是文件系统中的一个容器,保存了一组文件和其他一些目录。在 实现中,用成员变量children,用来保存目录中所有子目录项的INode对象。

    private List<INode> children 
    
    • 子目录项相关方法
      方法差不多都是操作 children 这个集合,包括``
      public boolean addChild() {
      }
      
      public boolean removeChild() {
      }
      
    INodeDirectoryWithQuota

    INodeDirectoryWithQuota 是 INodeDirectory 的一个子类,用于实现HDFS的配额机制。
    HDFS允许管理员为每个目录设置配额(HDFS的dfsadmin工具提供了修改目录配额的命令,该命令会修改INodeDirectoryWithQuota对象相应的成员变量)。配额有两种:

    • 节点配额
      用于限制目录下的名字数量,如果创建文件或目录时超过了该配额,操作失败。这个配额用于控制用于对于名字节点资源的占用,保存在成员变量nsQuota中。
    • 空间配额
      限制存储在目录树下的所有文件的总规模,空间配额保证用户不会过多咱用数据节点的资源,该配额由dsQuota变量保存。
    // 存在配额限制的目录节点,继承自目录节点
    class INodeDirectoryWithQuota extends INodeDirectory {
     
      private long nsQuota; // NameSpace quota 命名空间配额
      private long nsCount; // 名字空间计数
      private long dsQuota; // disk space quota 磁盘空间配额
      private long diskspace; // 磁盘空间占用大小
      
      void verifyQuota(long nsDelta, long dsDelta) throws QuotaExceededException {
        // 根据误差值计算新的计数值
        long newCount = nsCount + nsDelta;
        long newDiskspace = diskspace + dsDelta;
        if (nsDelta>0 || dsDelta>0) {
          // 判断新的值是否超出配额的值大小
          if (nsQuota >= 0 && nsQuota < newCount) {
            throw new NSQuotaExceededException(nsQuota, newCount);
          }
          if (dsQuota >= 0 && dsQuota < newDiskspace) {
            throw new DSQuotaExceededException(dsQuota, newDiskspace);
          }
        }
      }
    }
    
    INodeFile 类

    在文件系统目录树中,使用了 INodeFile 类抽象一个HDFS文件,INodeFile 类继承自 INodeWithAdditionalFields
    INodeFile 类中保存了HDFS文件最重要的两个信息:文件头和文件对应的数据块信息 字段。

    private long header  = 0L
    priavte BlockInfo[] blocks;
    

    header 字段保存了当前文件有多少个副本,以及文件数据块的大小。前4个比特用于保存存储策略,中间12个比特用于保存文件备份系数,后48个比特用于保存数据块的大小。
    blocks 字段是一个 BlockInfo 类型的数组,保存了当前文件对应的所有数据块信息,

    在block的操作中,一般都是针对最后一个block块的获取和添加一个block操作:

    Block getLastBlock() {
        if (this.blocks == null || this.blocks.length == 0)
          return null;
        return this.blocks[this.blocks.length - 1];
    }
    
    void addBlock(BlockInfo newblock) {
        if (this.blocks == null) {
          this.blocks = new BlockInfo[1];
          this.blocks[0] = newblock;
        } else {
          int size = this.blocks.length;
          BlockInfo[] newlist = new BlockInfo[size + 1];
          System.arraycopy(this.blocks, 0, newlist, 0, size);
          newlist[size] = newblock;
          this.blocks = newlist;
        }
    }
    
    INodeFileUnderConstrucation

    INodeFileUnderConstruction是INodeFile的子类。它是指处于构建状态的文件索引节点,当客户端为写或者追加数据打开HDFS文件时,该文件就处于构建状态,在HDFS的目录树中,相应的就是一个INodeFileUnderConstruction对象。

    INodeFileUnderConstuction相关:

    • clientName:发起文件写的客户端名称,这个属性也用于租约管理中,在HDFS中租约是名字节点维护的,给予客户端在一定期限内可以进行文件写操作的权限的合同。
    • clientMachine:客户端所在的主机。
    • clientNode:如果客户端运行在集群内的某一个数据节点上时,对应的数据节点信息,DatanodeDescriptor是名字节点内部使用的,用于保存数据节点信息的类,它继承自DatanodeInfo。
    • targets:最后一个数据块的数据流管道成员,即当前参与到写数据的数据节点的列表。primaryNodeIndex和-- ---lastRecoveryTime:都用于名字节点发起的数据块恢复,由名字节点发起的数据块恢复也叫租约恢复,这两个变量分别保存恢复时的主数据节点索引和恢复开始时间。
    class INodeFileUnderConstruction extends INodeFile { //处于构建状态的文件节点
      String clientName; // lease holder 写文件的客户端名称,也是这个租约的持有者
      private final String clientMachine; // 客户端所在的主机
                                          // 如果客户端同样存在于集群中,则记录所在的节点
      private final DatanodeDescriptor clientNode; // if client is a cluster node too.
      
      // 租约恢复时的节点
      private int primaryNodeIndex = -1; //the node working on lease recovery
    
      // 最后一个block块所处的节点组,又名数据流管道成员
      private DatanodeDescriptor[] targets = null;   //locations for last block
    
      // 最近租约恢复时间
      private long lastRecoveryTime = 0;
    }
    

    对于处于构建状态的节点来说,他的操作也是往最后一个block添加数据,并且保留了最后一个块的所在的数据节点列表。

    // 设置新的block块,并且为最后的块赋值新的targes节点
    synchronized void setLastBlock(BlockInfo newblock, DatanodeDescriptor[] newtargets
        ) throws IOException {
      // ......
      BlockInfo oldLast = blocks[blocks.length - 1];
      if (oldLast.getBlockId() != newblock.getBlockId()) {
        throw new IOException();
      }
      
      // 如果新的block时间比老block的还小的话,则进行警告
      if (oldLast.getGenerationStamp() > newblock.getGenerationStamp()) {
        NameNode.stateChangeLog.warn();
      }
     
      blocks[blocks.length - 1] = newblock;
      setTargets(newtargets);
      // 重置租约恢复时间,这样操作的话,下次租约检测时将会过期
      lastRecoveryTime = 0;
    }
    

    移除块操作也是移除最后一个block,数据节点列表也将被清空:

     void removeBlock(Block oldblock) throws IOException {
        if (blocks == null) {
          throw new IOException("Trying to delete non-existant block " + oldblock);
        }
        int size_1 = blocks.length - 1;
        if (!blocks[size_1].equals(oldblock)) {
          //如果不是最末尾一个块则将会抛异常
          throw new IOException("Trying to delete non-last block " + oldblock);
        }
     
        //copy to a new list
        BlockInfo[] newlist = new BlockInfo[size_1];
        System.arraycopy(blocks, 0, newlist, 0, size_1);
        blocks = newlist;
        
        // Remove the block locations for the last block.
        // 最后一个数据块所对应的节点组就被置为空了
        targets = null;
    
    INodeReference 类

    TODO

    FSDirectory 类

    Namenode 重要功能之一就是维护文件系统的命名空间,文件系统的命名空间是以"/"为根的整个目录树,是通过FSDirectory类来管理的。FSNamesystem 也提供了管理目录树结构的方法,但多是调用 FSDirectory 类的实现。FSNamesystem 在类方法的基础上添加了 editlog 日志记录的功能。

    在了解 之前,我们需要了解INodesInPath类,INodesInPath有两个重要的数据结构,分别是:

    INode[] inodes 
    byte[][] path
    

    比如我们的路径是"/A/B/C"那么path[0]对应于"A"转成的字节,inodes[0]对应"/A"这个inode。

    FSDirectory 的设计使用了门面模式,门面模式是指提供了一个统一的接口去访问多个子系统的多个不同的接口,它为子系统中的一组接口提供了统一的高层接口。

    • INodeDirectory rootDir
      整个文件系统目录树的根节点, 是INodeDirectory类型的 。
    • FSNamesystem namesystem
      Namenode的门面类, 这个类主要支持对数据块进行操作的一些方法, 例如addBlock()。
    • INodeMap inodeMap
      记录根目录下所有的INode,并维护INodeId ->INode的映射关系。
    • ReentrantReadWriteLock dirLock
      对目录树以及inodeMap字段操作的锁。
    • NameCache<ByteArray> nameCache
      将常用的name缓存下来, 以降低byte[]的使用, 并降低JVM heap的使用

    FSDirectory 类中的方法非常多,主要是封装了对文件系统目录树的操作,例如增,删,查,改。例如,添加一个文件:

    INodeFileUnderConstruction addFile( 
                    String path,
                    PermissionStatus permissions,
                    short replication,
                    long preferredBlockSize,
                    String clientName,
                    String clientMachine,
                    DatanodeDescriptor clientNode,
                    long generationStamp)  throws IOException {
        waitForReady(); // 等待该目录已经准备好,能够被使用
        long modTime = FSNamesystem.now(); // 取当前时间
        if (!mkdirs(new Path(path).getParent().toString(), permissions, true, modTime)) { // 创建path的父目录
          return null;
        }
        INodeFileUnderConstruction newNode = new INodeFileUnderConstruction(
                                     permissions,replication,
                                     preferredBlockSize, modTime, clientName, 
                                     clientMachine, clientNode); // 创建一个新的INode文件
        synchronized (rootDir) {
          newNode = addNode(path, newNode, -1, false); // 将newNode加入到namespace中去
        }
        if (newNode == null) { // 添加失败
          NameNode.stateChangeLog.info("DIR* FSDirectory.addFile: " +"failed to add "+path
     +" to the file system");
          return null;
        }
        fsImage.getEditLog().logOpenFile(path, newNode); // 将namespace中新添加INode的事务写入到FSImage对应的EditLog日志文件中
        NameNode.stateChangeLog.debug("DIR* FSDirectory.addFile: "  +path+" is added to the file system");
        return newNode;
      }
    

    addChild

    addBlock


    参考资料
    1、https://blog.csdn.net/zhanglong_4444/article/details/108444416
    2、https://blog.csdn.net/androidlushangderen/article/details/47427925

    相关文章

      网友评论

          本文标题:HDFS 文件系统

          本文链接:https://www.haomeiwen.com/subject/yzgmfdtx.html