HDFS2.X源码分析之:NameNode读文件原理

原文出自云台博客:http://yuntai.1kapp.com/?p=952

HDFS被设计成写一次,读多次的应用场景,这应该跟它的MapReduce机制是紧密关联的,通过对线上的读写比例监控,大概读写比是10:1,也验证了它设计的目标。

3.1 读流程分析

GFS论文提到的文件读取简单流程:


在HDFS中,具体流程如下图:


从上图,可以看出读取文件需要如下几个流程:

  1. 使用HDFS提供的客户端开发库Client,向远程的NameNode发起RPC请求;
  2. Namenode会视情况返回文件的部分或者全部block列表,对于每个block,Namenode都会返回有该block拷贝的DataNode地址;
  3. 客户端开发库Client会选取离客户端最接近的DataNode来读取block;如果客户端本身就是DataNode,那么将从本地直接获取数据.
  4. 读取完当前block的数据后,关闭与当前的DataNode连接,并为读取下一个block寻找最佳的DataNode;
  5. 当读完列表的block后,且文件读取还没有结束,客户端开发库会继续向Namenode获取下一批的block列表。
  6. 读取完一个block都会进行checksum验证,如果读取datanode时出现错误,客户端会通知Namenode,然后再从下一个拥有该block拷贝的datanode继续读;

在此,仅就NameNode接收客户端的RPC请求获取文件,文件相应的块,块位置进行分析,其调用代码流程为:NameNodeRpcServer.getBlockLocations()->FSNamesystem. getBlockLocations()->FSNamesystem. getBlockLocations()->DatanodeManager.sortLocatedBlocks()。具体流程图如下:

从上图可看出,接收Client的RPC请求方法getBlockLocations,检查该路径是否有读取权限,然后进入尝试获取块位置的第一次循环,如果是第一次,首先检查该NameNode节点状态是否允许读操作;然后从NameSpace获取该文件对于的inode对象;如果支持访问时间,且循环是第一次,则contine进入下一个循环,如果为第二次,修改该文件的访问时间,并且开始调用BlockManager类的方法createLocatedBlocks

for (int attempt = 0; attempt < 2; attempt++) {
      if (attempt == 0) { // first attempt is with readlock
        readLock();//第一次读取,获得读锁
      }  else { // second attempt is with  write lock
//第二次需要写锁,因为需要修改访问时间
        writeLock(); // writelock is needed to set accesstime
      }
      try {
        checkOperation(OperationCategory.READ);
        // if the namenode is in safemode, then do not update access time
        if (isInSafeMode()) {
          doAccessTime = false;
        }
        long now = now();
//从namespace获取该文件的inode对象
        INodeFile inode = dir.getFileINode(src);
        if (inode == null) {
          throw new FileNotFoundException("File does not exist: " + src);
        }
        assert !inode.isLink();
        if (doAccessTime && isAccessTimeSupported()) {
          if (now <= inode.getAccessTime() + getAccessTimePrecision()) {
            // if we have to set access time but we only have the readlock, then
            // restart this entire operation with the writeLock.
            if (attempt == 0) {
              continue;
            }
          }
          dir.setTimes(src, inode, -1, now, false);
        }
//获取文件块位置
        return blockManager.createLocatedBlocks(inode.getBlocks(),
            inode.computeFileSize(false), inode.isUnderConstruction(),
            offset, length, needBlockToken);
      } finally {
        if (attempt == 0) {
//第一次循环完毕,关闭读锁
          readUnlock();
        } else {
//第二次循环完毕,关闭写锁,此处锁的粒度是否可以变得更细呢,因为仅在设置访问时间处需要加锁
          writeUnlock();
        }
      }
    }
如果文件存在块,需要获取文件块信息及其位置信息

assert namesystem.hasReadOrWriteLock();
    if (blocks == null) {
      return null;
} else if (blocks.length == 0) {
//如果不存在块
      return new LocatedBlocks(0, isFileUnderConstruction,
          Collections.<LocatedBlock>emptyList(), null, false);
    } else {
      if (LOG.isDebugEnabled()) {
        LOG.debug("blocks = " + java.util.Arrays.asList(blocks));
      }
      final AccessMode mode = needBlockToken? AccessMode.READ: null;
//通过文件块列表,获取到块信息,及其副本所在的DN节点列表
      final List<LocatedBlock> locatedblocks = createLocatedBlockList(
          blocks, offset, length, Integer.MAX_VALUE, mode);
      final BlockInfo last = blocks[blocks.length - 1];
      //如果最后一个块状态为complete状态,最后位置=文件大小-最后一个块大小
      //否则最后位置是文件大小
      final long lastPos = last.isComplete()?
          fileSizeExcludeBlocksUnderConstruction - last.getNumBytes()
          : fileSizeExcludeBlocksUnderConstruction;
//获取文件最后一个块副本所在位置
      final LocatedBlock lastlb = createLocatedBlock(last, lastPos, mode);
      return new LocatedBlocks(
          fileSizeExcludeBlocksUnderConstruction, isFileUnderConstruction,
          locatedblocks, lastlb, last.isComplete());
对于方法createLocatedBlockList ,通过名字及其参数就知道,通过该方法,可以获取文件所有的块,块信息(包含块在文件内的起始位置),块所在的DN机器。方法createLocatedBlock用于处理每一个块,只改方法中,如果三个副本中有几个副本损坏了,则只将没有损坏的块加入machines集合,如果三个副本均被损坏,则将三个副本所在的DN均加入machines集合?这是为什么呢?难道在客户端通过标示corrupt再判断该块是否损坏?需要再确认下

。。。。。。。。。。。。。。。。。。。。。。。。 
//获取包含该块的DN节点数目
    final int numNodes = blocksMap.numNodes(blk);
    //是否整个块均被损坏:损坏块副本数等于节点数目
    final boolean isCorrupt = numCorruptNodes == numNodes;
    //如果某块所有的副本均损坏,则机器的数量为所有副本对应的机器数
    final int numMachines = isCorrupt ? numNodes: numNodes - numCorruptNodes;
    final DatanodeDescriptor[] machines = new DatanodeDescriptor[numMachines];
    int j = 0;
    if (numMachines > 0) {
      for(Iterator<DatanodeDescriptor> it = blocksMap.nodeIterator(blk);
          it.hasNext();) {
        final DatanodeDescriptor d = it.next();
        final boolean replicaCorrupt = corruptReplicas.isReplicaCorrupt(blk, d);
        //如果三个副本中有几个副本损坏了,则只将没有损坏的块加入machines集合
        //如果三个副本均被损坏,则将三个副本所在的DN均加入machines集合??这是为什么呢??
        //难道在客户端通过标示corrupt再判断该块是否损坏
        if (isCorrupt || (!isCorrupt && !replicaCorrupt))
          machines[j++] = d;
      }
    }
    assert j == machines.length :
      "isCorrupt: " + isCorrupt +
      " numMachines: " + numMachines +
      " numNodes: " + numNodes +
      " numCorrupt: " + numCorruptNodes +
      " numCorruptRepls: " + numCorruptReplicas;
    final ExtendedBlock eb = new ExtendedBlock(namesystem.getBlockPoolId(), blk);
    //eb:通过poolid跟块唯一标示一个块
    //machines:副本所在的机器
    return new LocatedBlock(eb, machines, pos, isCorrupt);
在获取到文件所有的LocatedBlock对象后,还需要对每个块所对应的DN节点按照与client的距离进行排序,如果离客户端越近,那么就越会排在数组的前面,如果该DN节点已经退役,则排行的数组的最后面,引入排序规则,有利用提高下载文件的速度,具体实现在DatanodeManager. sortLocatedBlocks()

 /** Sort the located blocks by the distance to the target host.
   * 通过DN与client的距离,对每个副本的的DN节点进行排序 ,以便于更快地获取到数据*/
  public void sortLocatedBlocks(final String targethost,
      final List<LocatedBlock> locatedblocks) {
    //sort the blocks
    final DatanodeDescriptor client = getDatanodeByHost(targethost);
    for (LocatedBlock b : locatedblocks) {
    //对于单个块对于的副本,按照DN节点与客户端的距离进行排序
      networktopology.pseudoSortByDistance(client, b.getLocations());
      // Move decommissioned datanodes to the bottom
      //如果块的DN节点列表内,有DN为退役状态,则将该DN节点移动DN列表尾部
      Arrays.sort(b.getLocations(), DFSUtil.DECOM_COMPARATOR);
    }   
  }


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值