HDFS读文件详解

通过对客户端简单读取数据的源码(见图3.1)的执行进行跟踪,可以窥探到客户端是如何读取到数据的。

图3.1 客户端简单读取数据的源码

下面开始解释第5行到第12代码:

第5行:根据文件的名字fileName构造一个Path类的对象path。

第6行:初始化一个Configuration变量conf。

第7行:根据path的成员函数getFileSystem()传入参数conf获得文件系统hdfs变量。

以上三行根据文件名,得到一个FileSystem类的对象hdfs,其实hdfs是FileSystem子类DistributedFileSystem的一个对象。此处hdfs为何为DistributedFileSystem一个对象,由于和需求无关,故不作深入解释,你只需知道hdfs是根据conf变量和文件名fileName决定的就可以了。

第8行:文件系统变量hdfs调用open()方法传入参数path得到一个输入流。

此处对open方法进行详细解释。

 

 

3.1 hdfs.open()详解

 

 

 

3.1.1 FileSystem.open(path)

 

Hdfs.open(path)是调用基类FileSystem的open(path)函数,图3.2为FileSystem的open()函数:

图3.2 FileSystem的open(path)函数

FileSystem.open(path)又调用FileSystem的抽象方法open(path, size)。

3.1.2 FileSystem.open(path, size)

图3.3为FileSystem的open(path, size)函数:

 

 

 

图3.3 FileSystem的抽象方法open(path, size)

抽象方法的实现是在子类中实现的,故实际调用的是子类的DistributedFileSystem实现的open(path, size)方法。

此处为何代码"FileSystem hdfs=path.getFileSystem(conf)"返回的是子类DistributedFileSystem的一个实例,这是根据path和conf共同决定的,具体分析,详见以后的分析,此处先不详述。

 

3.1.3 DistributedFileSystemd.open(path, size)

 

图3.4为DistributedFileSystem.open(path, size)的实现:

图3.4 DistributedFileSystemd的open(path, size)

图3.4中188行的dfs为DFSClient类的一个实例,DFSClient通过代理模式(用到了hadoop实现的RPC类)建立与NameNode的socket连接,具体细节不必深究,只需要知道DFSClient的功能即可。DFSClient的open(String src, int buffersize, boolean verifyChecksum, FileSystem.Statistics stats)方法返回一个DFSInputStream对象。

3.1.4 DFSClient.open(String src, int buffersize, boolean verifyChecksum, FileSystem.Statistics stats)

图3.5为DFSClient的open(String src, int buffersize, boolean verifyChecksum, FileSystem.Statistics stats)实现:

图3.5 DFSClient的open()方法

第478行检测clientRunning是否正在运行。

第480行根据DFSInputStream的构造函数:

DFSInputStream(String src, int buffersize, boolean verifyChecksum)构造一个DFSInputStream对象,DFSInputStream对象的构造详见3.1.5节。

 

3.1.5 DFS.checkOpen()

 

图3.6 DFS的checkOpen()方法

检测clientRunning是否正在运行,没有运行则抛出异常。

 

3.1.6 DFSInputStream.DFSInputStream(String src, int buffersize, boolean verifyChecksum)

 

DFSInputStream的构造函数见下图3.7:

图3. 7 DFSInputStream构造函数

DFSInputStream构造函数的前四行(即1682、1683、1684、1685行),分别在初始化一些参数;第1686行的openInfo(),它内部建立了与NameNode 的连接,通过调用DFSClient的callGetBlockLocations(namenode, src, 0, prefetchSize)

 

3.1.7 DFSInputStream.openInfo()

 

DFSInputStream. openInfo()详见下图3.8:

图3.8 DFSInputStream. openInfo()

图3.8的1693行调用DFSClient的方法callGetBlockLocations()。

callGetBlockLocations()函数的定义可以看出,它是调用了namenode 的getBlockLocations()方法;而namenode 的getBlockLocations()方法为:依据代理实现的对NameNode进行的远程过程调用,详见3.1.7节。

在第一次调用openInfo()时,DFSInputStream类的成员变量locatedBlocks为NULL,故不会执行图3.8的1700到1709这段代码。

第1710行updateBlockInfo(newInfo),在文件构造过程中,基于从datanode返回的block长度,更新最后一个block的大小。

第1711行,将DFSClient的callGetBlockLocations(namenode, src, 0, prefetchSize)方法返回的LocatedBlock变量赋给DFSClient的成员locatedBlocks。

3.1.8 DFSClient.callGetBlockLocations()

DFSClient的方法callGetBlockLocations()详见图3.9:

图3.9 DFSClient的callGetBlockLocations()

由图3. 9中callGetBlockLocations()函数的定义可以看出,它调用了namenode 的getBlockLocations()方法;而namenode 的getBlockLocations()方法为:依据代理实现的对NameNode进行的远程过程调用。namenode变量为一个ClientProtocol对象。

图3.10 ClientProtocol接口的getBlockLocations()

3.1.9 DFSInputStream.updateBlockInfo(LocatedBlocks newInfo)

图3. 11 DFSInputStream.updateBlockInfo(LocatedBlocks newInfo)

DFSInputStream的updateBlockInfo(LocatedBlocks newInfo),在文件构造过程中,基于从datanode返回的block长度,更新最后一个block的大小。

3.2 FSDataInputStream.read()详解

下面通过对读文件简单例子的分析,得出最外层面向开发者的FSDataInputStream.read(byte b[], int off, int len)的本质,最后帮助修改,解决需求(指定DataNode进行数据块读取)。

图3.12 客户端简单读取数据的源码

由图3.12第8行:

FSDataInputStream inputStream=hdfs.open(path);

可以看出open()函数返回了一个FSDataInputStream流对象,然后对这个流对象就行读操作,从表象看来确实如此,其实不然。

3.2.1 FSDataInputStream.read(byte b[], int off, int len)

究其实现可以看出,FSDataInputStream.read(byte b[], int off, int len)调用的是其父类DataInputStream的read(byte b[], int off, int len),图3.13为FSDataInputStream的继承关系:

图3.13 FSDataInputStream继承关系

3.2.2 DataInputStream.read(byte b[], int off, int len)

DataInputStream. read(byte b[], int off, int len)调用的是其成员的in的read(byte b[], int off, int len),见图3.14:

图3.14 DataInputStream. read(byte b[], int off, int len)

此处的read()需要特殊说明一下,in的read是从输入流in中读取长度为len的数据存放在b[] byte数组的off之后(包括off)的元素中,而不是我们通常所理解的那样。下面为英文解释:

The first byte read is stored into element b[off], the next one into b[off+1], and so on. The number of bytes read is, at most, equal to len. Let k be the number of bytes actually read; these bytes will be stored in elements b[off] through b[off+k-1], leaving elements b[off+k] through b[off+len-1] unaffected.

3.2.3 DataInputStream.in是什么

DataInputStream的成员in是什么呢,我们看其父类FilterInputStream,图3.15:

图3.15 FilterInputStream的成员变量in

    追溯到FilterInputStream类就知道了:

FSDataInputStream.read(byte b[], int off, int len)调用其父类DataInputStream的read(byte b[], int off, int len);

DataInputStream的read(byte b[], int off, int len)调用的是其成员变量InputStream in的read方法(byte b[], int off, int len)。

此处又出现了一个问题,这个类型为InputStream的in(真正的输入流)是怎么传进来的。

3.2.4 真正的输入流in

下面通过DFSInputStream类的继承关系和hdfs.open(path)的部分过程详解真正的输入流的构造生成过程。

图3.16 DFSInputStream类继承关系

图3.17 hdfs.open()获得流过程

我们由图3.17的hdfs.open()获得流过程的代码由下向上看(也可借鉴3.1.3小节)。

第30行构造了一个DFSInputStream变量并返回(此处记为a,方便下面描述解释)。

第15行的dfs.open(……)的返回值就是上一行所述的构造的DFSInputStream变量a。

第14行是根据15行构造的DFSInputStream变量a作为DFSClient的内部类DFSDataInputStream的构造函数的参数,下图3.18为DFSDataInputStream的构造函数的执行过程:

图3.18 DFSDataInputStream的构造函数的执行过程

由图3.18可以看出,执行到最后,其实是将DFSInputStream类的对象a赋给DFSDataInputStream类的成员变量in,类DFSDataInputStream类的成员变量in为InputStream类型。

故图3.17的第14行最后返回了一个(带有类型为DFSInputStream的输入流in的成员变量)DFSDataInputStream对象。

图3.17的第9行返回一个DFSDataInputStream对象。

图3.17的第8行返回一个DFSDataInputStream对象。

故图3.17的第1行:

FSDataInputStream inputStream=hdfs.open(path);

返回了一个(带有类型为DFSInputStream的输入流in的成员变量)DFSDataInputStream对象,到最后返回给它的直接父类FSDataInputStream的对象。下面一节对DFSInputStream的read()方法进行分析。

附件1

hdfs.open(path)执行流程:

posted on 2013-04-25 16:17  maybob  阅读(1818)  评论(0编辑  收藏  举报