DataNode节点的数据块管理(1)——FSDir

     在前面我说过,一个DataNode可以配置多个本地存储目录来存储与自己相关的数据,这些数据主要是NameNode分配给它并要它存储的文件数据块,另外,这些数据块是存储在本地存储目录的current/子目录下面的,那么DataNode就需要能对这些数据块进行管理了,而且还能够为一个数据块分配足够的存储空间。因此,HDFS专门设计了org.apache.hadoop.hdfs.server.datanode.FSDir类,每一个FSDir实例可以管理DataNode节点的一个存储路径中文件数据块。每一个数据块的信息对应两个文件,其中一个存数据,一个存储校验信息,如在DataNode的一个存储路径中:


     在上面的信息中,7938414676282962418是数据块的id,1002是数据块的版本号,blk_7938414676282962418存储的是该数据块的内容,blk_7938414676282962418_1002.meta存储的是该数据块的校验信息。

     对于FSDir类,它主要包含四个属性:

dir:存储路径的子目录current/

numBlocks:存储目录当前已经存储的数据块的数量;

children:目录current/的子目录;

lastChildIdx:存储上一个数据块的子目录序号;


     为了能够根据数据块的id快速找到对应的数据块文件(说白了就是在DataNode启动时能够快速地收集所有的Block信息好向NameNode报告),任何一个存储数据块的存储目录存储的数据块数量不能超过maxBlocksPerDir。具体来说是这样的:DataNode节点会首先把文件的数据块存储到存储路径的子目录current/下;当子目录current/中已经存储了maxBlocksPerDir个数据块之后,就会在目录current/下创建maxBlocksPerDir个子目录,然后从中选择一个子目录,把数据块存储到这个子目录中;如果选择的子目录也已经存储了maxBlocksPerDir个数据块,则又在这个子目录下创建maxBlocksPerDir个子目录,从这些子目录中选一个来存储数据块,就这样一次递归下去,直到存储路径的剩余存储空间不够存储一个数据块为止。哦,对了,maxBlocksPerDir的默认值是64,但也可以通过DataNode的配置文件来设置,它对应的配置选项是dsf.datanode.numblocks。

        ok,再来看看与FSDir相关联的类吧!


     每一个数据块被抽象成了一个Block对象,它主要有三个属性,blockId表示这个数据块的标识,它在整个HDFS集群中都是唯一的,numBytes表示这个数据块的大小,generationStamp表示这个数据块最近一次修改的时间戳。DataNodeBlockInfo存放的是Block在文件系统上的信息。它保存了Block存放的卷(FSVolume),文件名和detach状态。这里有必要解释一下detach状态:我们前面分析过,系统在升级/备份是会创建一个snapshot,snapshot的文件和current里面的数据块文件和数据块元文件是通过硬链接指向了相同的内容。当我们需要改变current里面的文件时,如果不进行detach操作,那么,修改的内容会影响snapshot里的文件,这是,我们就需要将对应的硬链接解除掉。方法很简单,就是在临时文件夹里复制文件,然后将临时文件改名为current里的对应文件,这样的话,crrent里的文件就和snapshot里的文件detach了(关于数据块的detach操作,我在后面还会讲到)。这样的技术也叫copy-on-write,是一种有效提高系统性能的方法。DataNodeBlockInfo中的detachBlock方法能够对应的数据文件和元数据文件进行detach操作。上面detach操作所提到的临时文件夹就是存储路径下的detach/子目录,这个子目录和对应的current/子目录在同一父路径下。

     当有数据块到达DataNode节点时,DataNode并不是马上在current/中为这个数据块选择合适的存储目录,而是先把它存放到存储路径的tmp/子目录下,当这个数据块被DataNode节点成功接受之后,才把它移动到current/下的合适目录中。因此,FSDir主要有以下几个方法:

1.添加一个数据块到curent/下

    为了让存储目录的子目录存储的数据块达到负载均衡,FSDir使用了lastChildIdx来循环的选择子目录存储数据块。

    public File addBlock(Block b, File src) throws IOException {
      //First try without creating subdirectories
      File file = addBlock(b, src, false, false);          
      return (file != null) ? file : addBlock(b, src, true, true);
    }

    private File addBlock(Block b, File src, boolean createOk,  boolean resetIdx) throws IOException {
      //直接将数据块保存到当前主目录下
      if (numBlocks < maxBlocksPerDir) {
        File dest = new File(dir, b.getBlockName());
        File metaData = getMetaFile( src, b );
        File newmeta = getMetaFile(dest, b);
        
        if ( ! metaData.renameTo( newmeta ) || ! src.renameTo( dest ) ) {
          throw new IOException( "could not move files for " + b + " from tmp to " + dest.getAbsolutePath() );
        }
        
        if (DataNode.LOG.isDebugEnabled()) {
          DataNode.LOG.debug("addBlock: Moved " + metaData + " to " + newmeta);
          DataNode.LOG.debug("addBlock: Moved " + src + " to " + dest);
        }
        //当前主目录存储的数据块加1       
         numBlocks += 1;
         return dest;
      }
            
      if (lastChildIdx < 0 && resetIdx) {
        //reset so that all children will be checked
        lastChildIdx = random.nextInt(children.length);              
      }
            
      if (lastChildIdx >= 0 && children != null) {
        //Check if any child-tree has room for a block.
        for (int i=0; i < children.length; i++) {
          int idx = (lastChildIdx + i)%children.length;
          File file = children[idx].addBlock(b, src, false, resetIdx);
          if (file != null) {
            lastChildIdx = idx;
            return file;
          }
        }
        lastChildIdx = -1;
      }
            
      if (!createOk) {
        return null;
      }
      //在当前主目录下创建若干个子目录来存储数据块
      if (children == null || children.length == 0) {
        children = new FSDir[maxBlocksPerDir];
        for (int idx = 0; idx < maxBlocksPerDir; idx++) {
          children[idx] = new FSDir(new File(dir, DataStorage.BLOCK_SUBDIR_PREFIX+idx));
        }
      }
            
      //now pick a child randomly for creating a new set of subdirs.
      lastChildIdx = random.nextInt(children.length);
      return children[ lastChildIdx ].addBlock(b, src, true, false);
    }


2.从数据块对应的元数据文件名中解析文件的时间戳

long getGenerationStampFromFile(File[] listdir, File blockFile) {
      String blockName = blockFile.getName();
      for (int j = 0; j < listdir.length; j++) {
        String path = listdir[j].getName();
        if (!path.startsWith(blockName)) {
          continue;
        }
        String[] vals = path.split("_");
        if (vals.length != 3) {     // blk, blkid, genstamp.meta
          continue;
        }
        String[] str = vals[2].split("\\.");
        if (str.length != 2) {
          continue;
        }
        return Long.parseLong(str[0]);
      }
      DataNode.LOG.warn("Block " + blockFile + " does not have a metafile!");
      
      return Block.GRANDFATHER_GENERATION_STAMP;
    }

3.获取存储目录下的所有数据块信息

/*采用递归的方式*/
public void getBlockInfo(TreeSet<Block> blockSet) {
      if (children != null) {
        for (int i = 0; i < children.length; i++) {
          children[i].getBlockInfo(blockSet);
        }
      }

      File blockFiles[] = dir.listFiles();
      for (int i = 0; i < blockFiles.length; i++) {
        if (Block.isBlockFilename(blockFiles[i])) {
          long genStamp = getGenerationStampFromFile(blockFiles, blockFiles[i]);
          blockSet.add(new Block(blockFiles[i], blockFiles[i].length(), genStamp));
        }
      }
      
    }
4.获取存储目录下的所有数据块的卷信息
void getVolumeMap(HashMap<Block, DatanodeBlockInfo> volumeMap, FSVolume volume) {
      if (children != null) {
        for (int i = 0; i < children.length; i++) {
          children[i].getVolumeMap(volumeMap, volume);
        }
      }

      File blockFiles[] = dir.listFiles();
      for (int i = 0; i < blockFiles.length; i++) {
        if (Block.isBlockFilename(blockFiles[i])) {
          long genStamp = getGenerationStampFromFile(blockFiles, blockFiles[i]);
           
          Block _block= new Block(blockFiles[i], blockFiles[i].length(), genStamp);
          volumeMap.put(_block, new DatanodeBlockInfo(volume, blockFiles[i]));
        }
      }
    }



  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
HDFS的DataNode节点之间的数据不均衡指的是在HDFS集群中,不同的DataNode节点存储数据量不一致。这可能导致某些节点负载过重,而其他节点负载较轻。 导致数据不均衡的主要原因有以下几点: 1. 初始复制:当数据进入HDFS时,会将其初始复制到不同的DataNode节点。由于网络延迟或节点性能差异等原因,可能导致某些节点复制的数据过多,而其他节点复制的数据较少。 2. 数据移动:当节点故障或离线时,HDFS会将其上存储数据移动到其他健康的节点上。这个过程可能导致一些节点存储数据数量过多,而其他节点数据较少。 为了解决数据不均衡的问题,HDFS采取了一些策略: 1. 副本平衡:HDFS会定期检查集群中各个节点上的数据数量,并采取副本平衡的措施。这意味着将数据从负载过重的节点移动到负载较轻的节点上,以实现数据均衡。 2. 调度:HDFS的调度器会根据各个节点上的剩余存储空间以及网络带宽等因素,决定将新的数据复制到哪些节点上,以实现负载均衡。 3. HDFS管理员操作:HDFS管理员可以手动干预,将一些数据从负载过重的节点移动到其他节点上,以实现数据均衡。 综上所述,数据不均衡是HDFS集群中的一个常见问题。通过副本平衡、调度和管理员操作等策略,HDFS可以实现数据的均衡分布,提高数据的可靠性和性能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值