第一次读比较大型的源码。有看到列一大幅类图的,一看头就大。有看到简要分析主要数据结构的,因为源码这么多,总不能事无具细地全讲一遍吧。但少了程序运行的脉络,总让人疑惑系统到底怎么运转起来的。我想,对于初学者,还是从程序运行的线索入手,按功能弄清每个模块的代码,然后连点成线,成面,最终从宏观再整理清整个系统架构,这样还能顺便学习一下大型系统的实现。当然,对于架构大牛,或者java大牛,可能就不需要这么细的去看,毕竟其中起粘合剂的部分对于大多数系统来说都是相似的。看一下宏观的架构分析就行了。
当然是初学者。我自己本身也只是java菜鸟,对面向对象还是有一点点体会的。毕竟hadoop是用java写的,至少也要有一点java基础什么的才能看得懂吧,虽然要求也不高。
分析过程也只能抓重点,不可能每一行代码都去深究。版本是hadoop 0.20.203 。好吧,我们从命令
hadoop fs -copyFromLocal xx xx
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.addFiledir是类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);
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
追踪一大圈,好晕。现在我们理一理思路。
我们知道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