Hadoop源码情景小析(1)fs -copyFromLocal

第一次读比较大型的源码。有看到列一大幅类图的,一看头就大。有看到简要分析主要数据结构的,因为源码这么多,总不能事无具细地全讲一遍吧。但少了程序运行的脉络,总让人疑惑系统到底怎么运转起来的。我想,对于初学者,还是从程序运行的线索入手,按功能弄清每个模块的代码,然后连点成线,成面,最终从宏观再整理清整个系统架构,这样还能顺便学习一下大型系统的实现。当然,对于架构大牛,或者java大牛,可能就不需要这么细的去看,毕竟其中起粘合剂的部分对于大多数系统来说都是相似的。看一下宏观的架构分析就行了。

适合的读者?

当然是初学者。我自己本身也只是java菜鸟,对面向对象还是有一点点体会的。毕竟hadoop是用java写的,至少也要有一点java基础什么的才能看得懂吧,虽然要求也不高。


分析过程也只能抓重点,不可能每一行代码都去深究。版本是hadoop 0.20.203 。好吧,我们从命令

hadoop fs -copyFromLocal xx xx

开始。hadoop支持多种文件系统,当然,这里我们关心的是hdfs。先上图,后面讲完再解释。

Hadoop源码情景小析(1)fs -copyFromLocal_第1张图片

hdfs 的shell命令是由类FsShell去执行的。

  public static void main(String argv[]) throws Exception {
    FsShell shell = new FsShell();
    int res;
    try {
      res = ToolRunner.run(shell, argv);
    } finally {
      shell.close();
    }
    System.exit(res);
  }
既然是命令行,需要一些命令解析的操作,所以由ToolRunner来协助。具体的功能就是由FsShell的成员函数来提供的。

void copyFromLocal(Path[] srcs, String dstf) throws IOException {
    Path dstPath = new Path(dstf);
    FileSystem dstFs = dstPath.getFileSystem(getConf());
    if (srcs.length == 1 && srcs[0].toString().equals("-"))
      copyFromStdin(dstPath, dstFs);
    else
      dstFs.copyFromLocalFile(false, false, srcs, dstPath);
  }
如果是copyFromStdin那就由FsShell来处理stdin的输入。其他文件系统的话dstFS.copyFromLocalFile 是FileSystem提供的。注意这里的FileSystem是从Path get的,因为要支持多种文件系统。

  public void copyFromLocalFile(boolean delSrc, boolean overwrite,
                                Path[] srcs, Path dst)
    throws IOException {
    Configuration conf = getConf();
    FileUtil.copy(getLocal(conf), srcs, this, dst, delSrc, overwrite, conf);
  }

需要先创建目录什么的,所以要使用FileUtil来协助。


FileUtil.copy使用FileSystem.create方法来生成FSDataOutputStream

对于DFS,就是使用DistributedFileSystem.create

  public FSDataOutputStream create(Path f, FsPermission permission,
    boolean overwrite,
    int bufferSize, short replication, long blockSize,
    Progressable progress) throws IOException {

    statistics.incrementWriteOps(1);
    return new FSDataOutputStream
       (dfs.create(getPathName(f), permission,
                   overwrite, replication, blockSize, progress, bufferSize),
        statistics);
  }
上面的dfs是类 DFSClient,其中的create方法

    OutputStream result = new DFSOutputStream(src, masked,
        overwrite, replication, blockSize, progress, buffersize,
        conf.getInt("io.bytes.per.checksum", 512));
    leasechecker.put(src, result);
然后FSDataOutputStream包装了一下DFSOutputStream

leasechecker是重要的类,这里先mark一下。

DFSOutputStream(DFSClient内部类)的构造

      try {
        namenode.create(
            src, masked, clientName, overwrite, replication, blockSize);
      } catch(RemoteException re) {
        throw re.unwrapRemoteException(AccessControlException.class,
                                       NSQuotaExceededException.class,
                                       DSQuotaExceededException.class);
      }
      streamer.start();
    }
streamer是类DataStreamer ,后面会提到。

The DataStreamer class is responsible for sending data packets to the datanodes in the pipeline.


NameNode#create

    namesystem.startFile(src,
        new PermissionStatus(UserGroupInformation.getCurrentUser().getShortUserName(),
            null, masked),
        clientName, clientMachine, overwrite, replication, blockSize);
namesystem是类FSNamesystem

而startFile最终调用startFileInternal

// increment global generation stamp
        long genstamp = nextGenerationStamp();
        INodeFileUnderConstruction newNode = dir.addFile 
dir是类FSDirectory。


扯了一圈,拿到FSDataOutputStream后,最终的复制IOUtils.copyBytes --> FSOutputSummer.write(加上checksum) ->FSDataOutputStream#writeChunk

FSDataOutputStream#writeChunk

 currentPacket = new Packet(packetSize, chunksPerPacket,
                                     bytesCurBlock);
//......
        currentPacket.writeChecksum(checksum, 0, cklen);
        currentPacket.writeData(b, offset, len);

        // If packet is full, enqueue it for transmission

//......

          dataQueue.addLast(currentPacket);

而DataStreamer daemon负责写这些dataQueue,也就是前面提到的streamer变量。当然,写入就是写入到datanone啦。
 try {
              // get packet to be sent.
              one = dataQueue.getFirst();
              long offsetInBlock = one.offsetInBlock;
  
              // get new block from namenode.
              if (blockStream == null) {
                LOG.debug("Allocating new block");
                nodes = nextBlockOutputStream(src); 
注意nextBlockOutputStream,获取datanode的block


追踪一大圈,好晕。现在我们理一理思路。

Hadoop源码情景小析(1)fs -copyFromLocal_第2张图片

我们知道HDFS有namenode,datanode。对于调用者,需要一个类来处理这些联系,就是DFSClient啦。而namenode是保存文件系统的metadata,具体就是FSNamesystem这个类来实现的啦,而对外联系就由Namenode这个类来完成。而写入datanode就是靠DFSClient的DataStreamer daemon啦,写入前需要联系namenode得到block信息。


具体的数据结构分析可以看这里 http://www.cnblogs.com/wangyonghui/archive/2011/09/01/2162489.html



你可能感兴趣的:(java,数据结构,hadoop,Path,generation,statistics)