Hadoop核心源码剖析系列(五): 读数据

点击上方“数据与智能”,“星标或置顶公众号”

第一时间获取好内容

作者 | 吴邪   大数据4年从业经验,目前就职于广州一家互联网公司,负责大数据基础平台自研、离线计算&实时计算研究

编辑 | auroral-L

这篇文章我们分享HDFS读取数据的流程,相对于写数据流程来说,读数据的流程会简单不少,写完这一篇之后,对HDFS的核心代码剖析算是告一段落了,这一系列包含了NameNode的初始化、DataNode的初始、元数据管理、HDFS写数据流程、HDFS读数据流程五个核心部分,毕竟HDFS是一个百万行级别代码的技术架构,内容非常多,所以本系列只选取HDFS关键且核心的功能点来剖析。

 

HDFS读数据流程

图1:读数据流程图

 

  1. HDFS 客户端调用DistributedFileSystem 类(FileSystem的实现类)的open()方法。

  2. DistributedFileSystem 通过NameNodeRPC与NameNode建立通信,调用getBlockLocations()方法,请求block数据块的存储位置。

  3. DistributedFileSystem 返回一个FSDataInputStream对象给客户端,FSDataInputStream携带有block数据库的元数据信息,客户端调用FSDataInputStream的read()方法,请求存放目标block最近的DataNode节点。

  4. DataNode和NameNode之间以数据流的方式进行通信,保证客户端可以重复调用read()方法进行读取,选择就近的DataNode读取需要的数据块信息,如果发现读取的DataNode有异常,则尝试读取下一个DataNode的数据,直至读完最后一个数据块。

  5. 当读取完文件之后,调用close()方法关闭DataNode和NameNode连接。

 

实际上,从流程上可以看出HDFS读数据数据和写入数据总体的流程是差不多的,但是读取数据会简单一些,下面我们开始进行源码的分析。

 

读取数据

我们还是根据流程图的过程进行分析,这样整个过程更加清晰。

找到FSDataInputStream这个类的open()方法,传入数据路径。

/**
 * Opens an FSDataInputStream at the indicated Path.
 * @param f the file to open
 */
public FSDataInputStream open(Path f) throws IOException {
  return open(f, getConf().getInt("io.file.buffer.size", 4096));
}
/**
 * Opens an FSDataInputStream at the indicated Path.
 * @param f the file name to open
 * @param bufferSize the size of the buffer to be used.
 */
public abstract FSDataInputStream open(Path f, int bufferSize)
  throws IOException;

显而易见,open(...)方法中传入了文件路径以及配置文件中设置的文件缓冲区大小为4M。继续点进去open方法发现是一个抽象方法,那下一步就应该找到实现类的open(...)方法,根据流程图可以知道FileSystem的实现类就是DistributedFileSystem,不言而喻,直接定位到DistributedFileSystem这个类的open方法准没错。

图2:FileSystem的实现类和方法

@Override
public FSDataInputStream open(Path f, final int bufferSize)
    throws IOException {
  //计算读取操作的次数进行累加并记录
  statistics.incrementReadOps(1);
  //判断数据路径是绝对路径还是相对路径,不重要
  Path absF = fixRelativePart(f);
  return new FileSystemLinkResolver<FSDataInputStream>() {
    @Override
    public FSDataInputStream doCall(final Path p)
        throws IOException, UnresolvedLinkException {
      //重要代码,重点关注,跟写数据的套路差不多
      final DFSInputStream dfsis =
        dfs.open(getPathName(p), bufferSize, verifyChecksum);
      return dfs.createWrappedInputStream(dfsis);
    }
    @Override
    public FSDataInputStream next(final FileSystem fs, final Path p)
        throws IOException {
      return fs.open(p, bufferSize);
    }
  }.resolve(this, absF);
}

重点关注以上dfs.open(xxx)方法,调用之前会通过文件路径判断文件是否属于当前的文件系统。

/**
 * Create an input stream that obtains a nodelist from the namenode, and then
 * reads from all the right places. Creates inner subclass of InputStream that
 * does the right out-of-band work.
 */
public DFSInputStream open(String src, int buffersize, boolean verifyChecksum)
      throws IOException, UnresolvedLinkException {
    //检查文件是否处于打开状态,无关紧要的方法
   checkOpen();
   // Get block info from namenode,从namenode获取block信息
   TraceScope scope = getPathTraceScope("newDFSInputStream", src);
   try {
      return new DFSInputStream(this, src, verifyChecksum);
   } finally {
      scope.close();
   }
}


方法的最后返回了DFSInputStream(xxx)这个构造函数,并且在构造函数中调用了openInfo()方法。

DFSInputStream(DFSClient dfsClient, String src, boolean verifyChecksum
               ) throws IOException, UnresolvedLinkException {
  this.dfsClient = dfsClient;
  this.verifyChecksum = verifyChecksum;
  this.src = src;
  synchronized (infoLock) {
    this.cachingStrategy = dfsClient.getDefaultReadCachingStrategy();
  }
  openInfo();
}
/**
 * Grab the open-file info from namenode
 * 从namenode获取要打开的文件对应的blcok信息
 */
void openInfo() throws IOException, UnresolvedLinkException {
  synchronized(infoLock) {
  //划重点,对应流程图的步骤二,从namenode获取block信息
    lastBlockBeingWrittenLength = fetchLocatedBlocksAndGetLastBlockLength();
    int retriesForLastBlockLength = dfsClient.getConf().retryTimesForGetLastBlockLength;
    //为了保证读取成功,特意用了while循环增强,循环调用fetchLocatedBlocksAndGetLastBlockLength()
    while (retriesForLastBlockLength > 0) {
      // Getting last block length as -1 is a special case. When cluster
      // restarts, DNs may not report immediately. At this time partial block
      // locations will not be available with NN for getting the length. Lets
      // retry for 3 times to get the length.
      if (lastBlockBeingWrittenLength == -1) {
        DFSClient.LOG.warn("Last block locations not available. "
            + "Datanodes might not have reported blocks completely."
            + " Will retry for " + retriesForLastBlockLength + " times");
        waitFor(dfsClient.getConf().retryIntervalForGetLastBlockLength);
        lastBlockBeingWrittenLength = fetchLocatedBlocksAndGetLastBlockLength();
      } else {
        break;
      }
      retriesForLastBlockLength--;
    }
    if (retriesForLastBlockLength == 0) {
      throw new IOException("Could not obtain the last block locations.");
    }
  }
}

对应流程图步骤二的getBlockLocations方法,详情请看fetchLocatedBlocksAndGetLastBlockLength()方法。

private long fetchLocatedBlocksAndGetLastBlockLength() throws IOException {
//调用DFSClient的getLocatedBlocks方法,通过文件路径获取blcok存储位置信息
  final LocatedBlocks newInfo = dfsClient.getLocatedBlocks(src, 0);
  if (DFSClient.LOG.isDebugEnabled()) {
    DFSClient.LOG.debug("newInfo = " + newInfo);
  }
  if (newInfo == null) {
    throw new IOException("Cannot open filename " + src);
  }
  if (locatedBlocks != null) {
    Iterator<LocatedBlock> oldIter = locatedBlocks.getLocatedBlocks().iterator();
    Iterator<LocatedBlock> newIter = newInfo.getLocatedBlocks().iterator();
    while (oldIter.hasNext() && newIter.hasNext()) {
      if (! oldIter.next().getBlock().equals(newIter.next().getBlock())) {
        throw new IOException("Blocklist for " + src + " has changed!");
      }
    }
  }
  locatedBlocks = newInfo;
  long lastBlockBeingWrittenLength = 0;
  if (!locatedBlocks.isLastBlockComplete()) {
    final LocatedBlock last = locatedBlocks.getLastLocatedBlock();
    if (last != null) {
      if (last.getLocations().length == 0) {
        if (last.getBlockSize() == 0) {
          // if the length is zero, then no data has been written to
          // datanode. So no need to wait for the locations.
          return 0;
        }
        return -1;
      }
      final long len = readBlockLength(last);
      last.getBlock().setNumBytes(len);
      lastBlockBeingWrittenLength = len; 
    }
  }


  fileEncryptionInfo = locatedBlocks.getFileEncryptionInfo();


  return lastBlockBeingWrittenLength;
}

重点关注DFSClient的getBlockLocations()方法,从namenode获取block位置信息。

public LocatedBlocks getLocatedBlocks(String src, long start) throws IOException {
   return getLocatedBlocks(src, start, dfsClientConf.prefetchSize);
}


/*
 * This is just a wrapper around callGetBlockLocations, but non-static so that
 * we can stub it out for tests.
 */
@VisibleForTesting
public LocatedBlocks getLocatedBlocks(String src, long start, long length) throws IOException {
   TraceScope scope = getPathTraceScope("getBlockLocations", src);
   try {
      return callGetBlockLocations(namenode, src, start, length);
   } finally {
      scope.close();
   }
}


/**
 * @see ClientProtocol#getBlockLocations(String, long, long)
 */
static LocatedBlocks callGetBlockLocations(ClientProtocol namenode, String src, long start, long length)
      throws IOException {
   try {
   //通过RPC远程调用NameNodeRPCServer
      return namenode.getBlockLocations(src, start, length);
   } catch (RemoteException re) {
      throw re.unwrapRemoteException(AccessControlException.class, FileNotFoundException.class,
            UnresolvedPathException.class);
   }
}


@Idempotent
public LocatedBlocks getBlockLocations(String src,
                                       long offset,
                                       long length) 
    throws AccessControlException, FileNotFoundException,
    UnresolvedLinkException, IOException;

可以看到返回的是LocatedBlocks对象,包含了List<LocatedBlock> blocks,封装了block的信息,以及block在文件中的偏移量,还有block对应DataNode的位置信息。原理上是RPC调用了NameNodeRPCServer的getBlockLocations()方法。

@Override // ClientProtocol
public LocatedBlocks getBlockLocations(String src, 
                                        long offset, 
                                        long length) 
    throws IOException {
  //检查NameNode是否已经启动
  checkNNStartup();
  //计算获取到block信息并记录变化
  metrics.incrGetBlockLocations();
  return namesystem.getBlockLocations(getClientMachine(), 
                                      src, offset, length);
}


/**
 * Get block locations within the specified range.
 * @see ClientProtocol#getBlockLocations(String, long, long)
 */
LocatedBlocks getBlockLocations(String clientMachine, String src,
    long offset, long length) throws IOException {
  checkOperation(OperationCategory.READ);
  //创建Block信息结果对象
  GetBlockLocationsResult res = null;
  readLock();
  try {
    checkOperation(OperationCategory.READ);
    //获取block位置信息
    res = getBlockLocations(src, offset, length, true, true);
  } catch (AccessControlException e) {
    logAuditEvent(false, "open", src);
    throw e;
  } finally {
    readUnlock();
  }
  logAuditEvent(true, "open", src);
  if (res.updateAccessTime()) {
    writeLock();
    final long now = now();
    try {
      checkOperation(OperationCategory.WRITE);
      INode inode = res.iip.getLastINode();
      boolean updateAccessTime = now > inode.getAccessTime() +
          getAccessTimePrecision();
      if (!isInSafeMode() && updateAccessTime) {
        boolean changed = FSDirAttrOp.setTimes(dir,
            inode, -1, now, false, res.iip.getLatestSnapshotId());
        if (changed) {
          getEditLog().logTimes(src, -1, now);
        }
      }
    } catch (Throwable e) {
      LOG.warn("Failed to update the access time of " + src, e);
    } finally {
      writeUnlock();
    }
  }
  //将获取到的block信息赋值给LocatedBlocks 
  LocatedBlocks blocks = res.blocks;
  if (blocks != null) {
    blockManager.getDatanodeManager().sortLocatedBlocks(
        clientMachine, blocks.getLocatedBlocks());
    // lastBlock is not part of getLocatedBlocks(), might need to sort it too
    //获取到最后一个Block的位置信息
    LocatedBlock lastBlock = blocks.getLastLocatedBlock();
    if (lastBlock != null) {
      ArrayList<LocatedBlock> lastBlockList = Lists.newArrayList(lastBlock);
      blockManager.getDatanodeManager().sortLocatedBlocks(
          clientMachine, lastBlockList);
    }
  }
  //返回LocatedBlocks对象,封装了目标文件包含的所有block的位置信息
  return blocks;
}


以上就完成了步骤二获取block位置信息的分析,同样的将返回的DFSInputStream对象传递给createWrappedInputStream(...)方法中进行再次封装。接下来根据NameNode返回的LocatedBlocks对象信息,请求FSDataInputStream的 read()方法。

/**
 * Read bytes from the given position in the stream to the given buffer.
 *
 * @param position  position in the input stream to seek
 * @param buffer    buffer into which data is read
 * @param offset    offset into the buffer in which data is written
 * @param length    maximum number of bytes to read
 * @return total number of bytes read into the buffer, or <code>-1</code>
 *         if there is no more data because the end of the stream has been
 *         reached
 */
@Override
public int read(long position, byte[] buffer, int offset, int length)
  throws IOException {
  return ((PositionedReadable)in).read(position, buffer, offset, length);
}

FSDataInputStream会调用其封装的DFSInputStream的read(xxx)方法。

/**
 * Read bytes starting from the specified position.
 * 
 * @param position start read from this position
 * @param buffer read buffer
 * @param offset offset into buffer
 * @param length number of bytes to read
 * 
 * @return actual number of bytes read
 */
@Override
public int read(long position, byte[] buffer, int offset, int length)
    throws IOException {
  TraceScope scope =
      dfsClient.getPathTraceScope("DFSInputStream#byteArrayPread", src);
  try {
    return pread(position, buffer, offset, length);
  } finally {
    scope.close();
  }
}




private int pread(long position, byte[] buffer, int offset, int length)
    throws IOException {
  // sanity checks,检查文件系统是否运行中
  dfsClient.checkOpen();
  if (closed.get()) {
    throw new IOException("Stream closed");
  }
  failures = 0;
  //获取LocatedBlocks的长度
  long filelen = getFileLength();
  if ((position < 0) || (position >= filelen)) {
    return -1;
  }
  int realLen = length;
  if ((position + length) > filelen) {
    realLen = (int)(filelen - position);
  }
  
  // determine the block and byte range within the block
  // corresponding to position and realLen
  //得到从offset到offset + length范围内的block列表
  List<LocatedBlock> blockRange = getBlockRange(position, realLen);
  int remaining = realLen;
  Map<ExtendedBlock,Set<DatanodeInfo>> corruptedBlockMap 
    = new HashMap<ExtendedBlock, Set<DatanodeInfo>>();
   //对block列表进行遍历,读取需要的block数据,因为需要的数据不一定是存在一个block列表中,通常分布在多个block
  for (LocatedBlock blk : blockRange) {
    long targetStart = position - blk.getStartOffset();
    long bytesToRead = Math.min(remaining, blk.getBlockSize() - targetStart);
    try {
      if (dfsClient.isHedgedReadsEnabled()) {
        hedgedFetchBlockByteRange(blk, targetStart, targetStart + bytesToRead
            - 1, buffer, offset, corruptedBlockMap);
      } else {
        fetchBlockByteRange(blk, targetStart, targetStart + bytesToRead - 1,
            buffer, offset, corruptedBlockMap);
      }
    } finally {
      // Check and report if any block replicas are corrupted.
      // BlockMissingException may be caught if all block replicas are
      // corrupted.
      reportCheckSumFailure(corruptedBlockMap, blk.getLocations().length);
    }
    remaining -= bytesToRead;
    position += bytesToRead;
    offset += bytesToRead;
  }
  assert remaining == 0 : "Wrong number of bytes read.";
  if (dfsClient.stats != null) {
    dfsClient.stats.incrementBytesRead(realLen);
  }
  return realLen;
}

分析getBlockRange(xxx)方法,通过指定的范围从namenode获取数据,优先从缓存中获取。

/**
 * Get blocks in the specified range.
 * Fetch them from the namenode if not cached. This function
 * will not get a read request beyond the EOF.
 * @param offset starting offset in file
 * @param length length of data
 * @return consequent segment of located blocks
 * @throws IOException
 */
private List<LocatedBlock> getBlockRange(long offset,
    long length)  throws IOException {
  // getFileLength(): returns total file length
  // locatedBlocks.getFileLength(): returns length of completed blocks
  //通常offset是要小于文件长度的
  if (offset >= getFileLength()) {
    throw new IOException("Offset: " + offset +
      " exceeds file length: " + getFileLength());
  }
  //之前有说到,block的状态有两种,一种是complete写入完成的,另一种是uncomplete构建中的状态
  synchronized(infoLock) {
    final List<LocatedBlock> blocks;
    //得到locatedBlocks的长度
    final long lengthOfCompleteBlk = locatedBlocks.getFileLength();
    final boolean readOffsetWithinCompleteBlk = offset < lengthOfCompleteBlk;
    final boolean readLengthPastCompleteBlk = offset + length > lengthOfCompleteBlk;


    if (readOffsetWithinCompleteBlk) {
      //get the blocks of finalized (completed) block range,
      blocks = getFinalizedBlockRange(offset,
        Math.min(length, lengthOfCompleteBlk - offset));
    } else {
      blocks = new ArrayList<LocatedBlock>(1);
    }


    // get the blocks from incomplete block range
    if (readLengthPastCompleteBlk) {
       blocks.add(locatedBlocks.getLastLocatedBlock());
    }


    return blocks;
  }
}


/**
 * Get blocks in the specified range.
 * Includes only the complete blocks.
 * Fetch them from the namenode if not cached.
 */
private List<LocatedBlock> getFinalizedBlockRange(
    long offset, long length) throws IOException {
  synchronized(infoLock) {
    assert (locatedBlocks != null) : "locatedBlocks is null";
    List<LocatedBlock> blockRange = new ArrayList<LocatedBlock>();
    // search cached blocks first
    //首先会先从缓存的locatedBlocks中查找offset所在的block在缓存链表中的位置
    int blockIdx = locatedBlocks.findBlock(offset);
    if (blockIdx < 0) { // block is not cached,无缓存
      blockIdx = LocatedBlocks.getInsertIndex(blockIdx);
    }
    long remaining = length;
    long curOff = offset;
    while(remaining > 0) {
      LocatedBlock blk = null;
      if(blockIdx < locatedBlocks.locatedBlockCount())
        //根据blcokIdx找到block
        blk = locatedBlocks.get(blockIdx);
        //说明没有缓存,从NameNode查找block并添加到缓存
      if (blk == null || curOff < blk.getStartOffset()) {
        LocatedBlocks newBlocks;
        newBlocks = dfsClient.getLocatedBlocks(src, curOff, remaining);
        locatedBlocks.insertRange(blockIdx, newBlocks.getLocatedBlocks());
        continue;
      }
      assert curOff >= blk.getStartOffset() : "Block not found";
      blockRange.add(blk);
      long bytesRead = blk.getStartOffset() + blk.getBlockSize() - curOff;
      remaining -= bytesRead;
      curOff += bytesRead;
      //继续读取下一个block
      blockIdx++;
    }
    return blockRange;
  }
}

其中我们看一下pread(xxx)方法下引用的fetchBlockByteRange(...)方法。

private void fetchBlockByteRange(LocatedBlock block, long start, long end,
    byte[] buf, int offset,
    Map<ExtendedBlock, Set<DatanodeInfo>> corruptedBlockMap)
    throws IOException {
    //通过偏移量获取LocatedBlock 对象
  block = getBlockAt(block.getStartOffset());
  //熟悉的while循环,为了最大程度上保证成功获取数据
  while (true) {
  //选择就近的一个DataNode进行读取
    DNAddrPair addressPair = chooseDataNode(block, null);
    try {
   //通过选择的DataNode,根据block的起始偏移量开始获取数据,获取完成后return;结束循环
      actualGetFromOneDataNode(addressPair, block, start, end, buf, offset,
          corruptedBlockMap);
      return;
    } catch (IOException e) {
      // Ignore. Already processed inside the function.
      // Loop through to try the next node.
    }
  }
}

执行完actualGetFromOneDataNode(...)方法获取完数据之后会执行close()方法结束连接。

private void actualGetFromOneDataNode(final DNAddrPair datanode,
    LocatedBlock block, final long start, final long end, byte[] buf,
    int offset, Map<ExtendedBlock, Set<DatanodeInfo>> corruptedBlockMap)
    throws IOException {
  DFSClientFaultInjector.get().startFetchFromDatanode();
  int refetchToken = 1; // only need to get a new access token once
  int refetchEncryptionKey = 1; // only need to get a new encryption key once


  while (true) {
    // cached block locations may have been updated by chooseDataNode()
    // or fetchBlockAt(). Always get the latest list of locations at the
    // start of the loop.
    CachingStrategy curCachingStrategy;
    boolean allowShortCircuitLocalReads;
    block = getBlockAt(block.getStartOffset());
    synchronized(infoLock) {
      curCachingStrategy = cachingStrategy;
      allowShortCircuitLocalReads = !shortCircuitForbidden();
    }
    DatanodeInfo chosenNode = datanode.info;
    InetSocketAddress targetAddr = datanode.addr;
    StorageType storageType = datanode.storageType;
    //初始化BlockReader
    BlockReader reader = null;


    try {
      DFSClientFaultInjector.get().fetchFromDatanodeException();
      Token<BlockTokenIdentifier> blockToken = block.getBlockToken();
      int len = (int) (end - start + 1);
      //reader负责从DataNode读取数据,构建socket连接到DataNode
      reader = new BlockReaderFactory(dfsClient.getConf()).
          setInetSocketAddress(targetAddr).
          setRemotePeerFactory(dfsClient).
          setDatanodeInfo(chosenNode).
          setStorageType(storageType).
          setFileName(src).
          setBlock(block.getBlock()).
          setBlockToken(blockToken).
          setStartOffset(start).
          setVerifyChecksum(verifyChecksum).
          setClientName(dfsClient.clientName).
          setLength(len).
          setCachingStrategy(curCachingStrategy).
          setAllowShortCircuitLocalReads(allowShortCircuitLocalReads).
          setClientCacheContext(dfsClient.getClientContext()).
          setUserGroupInformation(dfsClient.ugi).
          setConfiguration(dfsClient.getConfiguration()).
          build();
       //读取数据
      int nread = reader.readAll(buf, offset, len);
      updateReadStatistics(readStatistics, nread, reader);


      if (nread != len) {
        throw new IOException("truncated return from reader.read(): " +
                              "excpected " + len + ", got " + nread);
      }
      DFSClientFaultInjector.get().readFromDatanodeDelay();
      return;
    } catch (ChecksumException e) {
      String msg = "fetchBlockByteRange(). Got a checksum exception for "
          + src + " at " + block.getBlock() + ":" + e.getPos() + " from "
          + chosenNode;
      DFSClient.LOG.warn(msg);
      // we want to remember what we have tried
      
      addIntoCorruptedBlockMap(block.getBlock(), chosenNode, corruptedBlockMap);
      //如果读取失败,则将该DataNode标记位异常节点
      addToDeadNodes(chosenNode);
      throw new IOException(msg);
    } catch (IOException e) {
      if (e instanceof InvalidEncryptionKeyException && refetchEncryptionKey > 0) {
        DFSClient.LOG.info("Will fetch a new encryption key and retry, " 
            + "encryption key was invalid when connecting to " + targetAddr
            + " : " + e);
        // The encryption key used is invalid.
        refetchEncryptionKey--;
        dfsClient.clearDataEncryptionKey();
        continue;
      } else if (refetchToken > 0 && tokenRefetchNeeded(e, targetAddr)) {
        refetchToken--;
        try {
          fetchBlockAt(block.getStartOffset());
        } catch (IOException fbae) {
          // ignore IOE, since we can retry it later in a loop
        }
        continue;
      } else {
        String msg = "Failed to connect to " + targetAddr + " for file "
            + src + " for block " + block.getBlock() + ":" + e;
        DFSClient.LOG.warn("Connection failure: " + msg, e);
        addToDeadNodes(chosenNode);
        throw new IOException(msg);
      }
    } finally {
      if (reader != null) {
        reader.close();
      }
    }
  }
}

本文分析了HDFS写数据流程,从HDFS客户端调用DistributedFileSystem 和FSDataInputStream这两个核心类的方法,通过NameNodeRPC调用NameNode对应的实现方法,获取目标block数据块的元数据信息,通过得到的元数据信息从对应的DataNode节点读取数据,直到读完最后一个block,关闭DataNode和NameNode之间的数据流,完成数据读取。至此完成了HDFS读取数据核心源码的剖析,可以与上一篇文章《Hadoop核心源码剖析(写数据)》串起来一起读,或许会有更多的收获,有关更多细节可以自己动手深究。

总结

本篇文章也是Hadoop HDFS核心源码剖析的完结篇,HDFS主要的核心功能模块基本都涉及到了,因为篇幅有限,可能写得不是很细致,主要还是介绍自己对HDFS核心源码的解读,分享自己的源码阅读技巧,希望对学习路上的小伙伴有所启发,接下来会先从其他组件入手,开启新的一轮分享,有兴趣的小伙伴可以关注本公众号,持续跟进源码系列。同时你也可以在本篇末尾留言,说一下你的想法和期待。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第1章 HDFS 1 1.1 HDFS概述 1 1.1.1 HDFS体系结构 1 1.1.2 HDFS基本概念 2 1.2 HDFS通信协议 4 1.2.1 Hadoop RPC接口 4 1.2.2 流式接口 20 1.3 HDFS主要流程 22 1.3.1 HDFS客户端读流程 22 1.3.2 HDFS客户端写流程 24 1.3.3 HDFS客户端追加写流程 25 1.3.4 Datanode启动、心跳以及执行名字节点指令流程 26 1.3.5 HA切换流程 27 第2章 Hadoop RPC 29 2.1 概述 29 2.1.1 RPC框架概述 29 2.1.2 Hadoop RPC框架概述 30 2.2 Hadoop RPC的使用 36 2.2.1 Hadoop RPC使用概述 36 2.2.2 定义RPC协议 40 2.2.3 客户端获取Proxy对象 45 2.2.4 服务器获取Server对象 54 2.3 Hadoop RPC实现 63 2.3.1 RPC类实现 63 2.3.2 Client类实现 64 2.3.3 Server类实现 76 第3章 Namenode(名字节点) 88 3.1 文件系统树 88 3.1.1 INode相关类 89 3.1.2 Feature相关类 102 3.1.3 FSEditLog类 117 3.1.4 FSImage类 138 3.1.5 FSDirectory类 158 3.2 数据块管理 162 3.2.1 Block、Replica、BlocksMap 162 3.2.2 数据块副本状态 167 3.2.3 BlockManager类(done) 177 3.3 数据节点管理 211 3.3.1 DatanodeDescriptor 212 3.3.2 DatanodeStorageInfo 214 3.3.3 DatanodeManager 217 3.4 租约管理 233 3.4.1 LeaseManager.Lease 233 3.4.2 LeaseManager 234 3.5 缓存管理 246 3.5.1 缓存概念 247 3.5.2 缓存管理命令 247 3.5.3 HDFS集中式缓存架构 247 3.5.4 CacheManager类实现 248 3.5.5 CacheReplicationMonitor 250 3.6 ClientProtocol实现 251 3.6.1 创建文件 251 3.6.2 追加写文件 254 3.6.3 创建新的数据块 257 3.6.4 放弃数据块 265 3.6.5 关闭文件 266 3.7 Namenode的启动和停止 268 3.7.1 安全模式 268 3.7.2 HDFS High Availability 276 3.7.3 名字节点的启动 301 3.7.4 名字节点的停止 306 第4章 Datanode(数据节点) 307 4.1 Datanode逻辑结构 307 4.1.1 HDFS 1.X架构 307 4.1.2 HDFS Federation 308 4.1.3 Datanode逻辑结构 310 4.2 Datanode存储 312 4.2.1 Datanode升级机制 312 4.2.2 Datanode磁盘存储结构 315 4.2.3 DataStorage实现 317 4.3 文件系统数据集 334 4.3.1 Datanode上数据块副本的状态 335 4.3.2 BlockPoolSlice实现 335 4.3.3 FsVolumeImpl实现 342 4.3.4 FsVolumeList实现 345 4.3.5 FsDatasetImpl实现 348 4.4 BlockPoolManager 375 4.4.1 BPServiceActor实现 376 4.4.2 BPOfferService实现 389 4.4.3 BlockPoolManager实现 396 4.5 流式接口 398 4.5.1 DataTransferProtocol定义 398 4.5.2 Sender和Receiver 399 4.5.3 DataXceiverServer 403 4.5.4 DataXceiver 406 4.5.5 读数据 408 4.5.6 写数据(done) 423 4.5.7 数据块替换、数据块拷贝和读数据块校验 437 4.5.8 短路读操作 437 4.6 数据块扫描器 437 4.6.1 DataBlockScanner实现 438 4.6.2 BlockPoolSliceScanner实现 439 4.7 DirectoryScanner 442 4.8 DataNode类的实现 443 4.8.1 DataNode的启动 444 4.8.2 DataNode的关闭 446 第5章 HDFS客户端 447 5.1 DFSClient实现 447 5.1.1 构造方法 448 5.1.2 关闭方法 449 5.1.3 文件系统管理与配置方法 450 5.1.4 HDFS文件与操作方法 451 5.1.5 HDFS文件读写方法 452 5.2 文件读操作与输入流 452 5.2.1 打开文件 452 5.2.2 读操作――DFSInputStream实现 461 5.3 文件短路读操作 481 5.3.1 短路读共享内存 482 5.3.2 DataTransferProtocol 484 5.3.3 DFSClient短路读操作流程 488 5.3.4 Datanode短路读操作流程 509 5.4 文件写操作与输出流 512 5.4.1 创建文件 512 5.4.2 写操作――DFSOutputStream实现 516 5.4.3 追加写操作 543 5.4.4 租约相关 546 5.4.5 关闭输出流 548 5.5 HDFS常用工具 549 5.5.1 FsShell实现 550 5.5.2 DFSAdmin实现 552

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值