Hadoop源码分析(10HDFS外壳)

14
Hadoop 源 10HDFS 源 作作 2013-08-11 “HDFS 作 ”作作作 作作作作作作 HDFS 作作 HDFS ,。 //作 一: FileSystem 作作作作 public class FileCopyWithProgress { public static void main(String[] args) throws Exception { String localSrc = args[0]; String dst = args[1]; InputStream in = new BufferedInputStream(new FileInputStream(localSrc)); Configuration conf = new Configuration(); //作 作 FileSystem 作作作 HDFS FileSystem fs = FileSystem.get(URI.create(dst), conf); OutputStream out = fs.create(new Path(dst), new Progressable() { //作作作作作作作作作 MapReduce 作作作作作作 public void progress() { System.out.print("."); } }); IOUtils.copyBytes(in, out, 4096, true); } } hadoop FileCopyWithProgress input/1.txt hdfs://localhost/user/hadoop/1.txt //作 作 FileSystem API 作作作作 public class FileSystemCat { public static void main(String[] args) throws Exception { String uri = args[0]; Configuration conf = new Configuration(); //作 作 FileSystem 作作作HDFS 1

Transcript of Hadoop源码分析(10HDFS外壳)

Page 1: Hadoop源码分析(10HDFS外壳)

Hadoop源码分析(10HDFS外壳)

作者:张孟志 日期:2013-08-11

“HDFS 外壳”将从系统的外部来了解 HDFS,通过程序员的编码角度来看

HDFS,即注重于编程接口。

先看两段代码://代码段一:使用 FileSystem 创建文件public class FileCopyWithProgress { public static void main(String[] args) throws Exception { String localSrc = args[0]; String dst = args[1]; InputStream in = new BufferedInputStream(new FileInputStream(localSrc)); Configuration conf = new Configuration(); //获取一个 FileSystem 的实例,HDFS FileSystem fs = FileSystem.get(URI.create(dst), conf); OutputStream out = fs.create(new Path(dst), new Progressable() { //存储过程的回调函数,MapReduce 中有重要作用 public void progress() { System.out.print("."); } }); IOUtils.copyBytes(in, out, 4096, true); }}命令行运行:hadoop FileCopyWithProgress input/1.txt hdfs://localhost/user/hadoop/1.txt

//代码段二:通过 FileSystem API 读取数据public class FileSystemCat { public static void main(String[] args) throws Exception { String uri = args[0]; Configuration conf = new Configuration();

1

Page 2: Hadoop源码分析(10HDFS外壳)

//获取一个 FileSystem 的实例,HDFS FileSystem fs = FileSystem.get(URI.create(uri), conf); InputStream in = null; try { in = fs.open(new Path(uri)); IOUtils.copyBytes(in, System.out, 4096, false); } finally { IOUtils.closeStream(in); } }}命令行运行: hadoop FileSystemCat hdfs://localhost/user/tom/quangle.txt

从上面两段代码可以看出,HDFS 暴露出来的用户接口是 FileSystem 这个

通用文件系统的抽象类。

FileSystem 这个很庞大、关联依赖的类也很多多,从下面的类图 就可以看

出来。虽说繁杂,但逻辑相对简单,比较容易理解。

2

张孟志, 13年8月11日,
Page 3: Hadoop源码分析(10HDFS外壳)

下面将简单介绍几个重要的类。

FileSystem

上文已说过 FileSystem 是一个通用文件系统的抽象基类,它可能被实现为

分布式文件系统或本地文件系统。

先来看其重要成员:

CACHE: 静态成员,对打开的文件系统实例做 cache,在计算机领域里面,

3

Page 4: Hadoop源码分析(10HDFS外壳)

cache 是非常重要的;

statisticsTable: 静态成员,保存各文件系统实例的统计信息;

key: 文件系统实例在 CACHE 中的键;

statistics: 文件系统实例在读写统计;

deleteOnExit: 退出时需要删除的文件,这个功能很实用,Java 里的文件也

有这么个功能;

clientFinalizer: 一个线程,用于在退出时关闭所有缓存的文件系统;

从其成员,我们可以看到 FileSystem 有两个功能:缓存和统计。

下面我们来瞧瞧 FileSystem 的方法,传统的文件系统里都会有的创建目录、

创建文件、打开文件、列举目录的文件、重命名文件、关闭文件等功能都覆盖

到,除此还有其它一些重要的方法:

getFileBlockLocations: 取得文件中某个区域的内容所在块(可能会存储在

多个块中)的位置

exists: 检查路径是否存在;

isFile: 检查给定路径是否是一个文件;4

Page 5: Hadoop源码分析(10HDFS外壳)

getContentSummary: 取得给定路径的统计情况,包括文件总大小、文件数

目和目录数目,会递归统计子目录的情况;

listStatus: 如果给定路径是目录,列举该目录的文件和子目录的状态;

globStatus: 返回匹配特定模式的所有文件,跟 Linux 的命令行很像,可以使

用通配来扩展;

getHomeDirectory: 取得用户的主目录;

(get/set)*etWorkingDirectory: 设置和取得当前工作目录;

copyFromLocalFile: 将文件从本地文件系统拷贝到当前文件系统;

copyToLocalFile: 将文件从当前文件系统拷贝到本地文件系统;

moveFromLocalFile: 将文件从本地文件系统移动到当前文件系统;

moveToLocalFile: 将文件从当前文件系统移到到本地文件系统;

getFileStatus: 取得文件的状态;

setPermission: 设置文件的访问权限,该方法为空;

setOwner: 设置文件所属的用户和组,该方法为空;

setTimes: 设置文件的修改时间和访问时间,该方法为空;5

Page 6: Hadoop源码分析(10HDFS外壳)

getAllStatistics: 取得所有文件系统的统计情况;

getStatistics: 取得某个特定文件系统的统计情况。

另外两个重要的静态方法:

get: 根据 URI 取得一个 FileSystem 实例,如果允许缓存,会中从缓存中取

出,否则将调用 createFileSystem 创建一个新实例;

createFileSystem: 以 URI 的 scheme 为键从配置中得到实现该 scheme 的类名

的值,然后创建一个新的 FileSystem 实例。

在 Hadoop 生态系统中,可能会经常用到 HDFS,也可能会经常见到如下代

码:FileSystem fs = FileSystem.get(URI.create(dst), conf);

代码的作用是通过一个 uri 来取得对应文件系统的实例。

FileSystem.Cache

用于缓存创建的文件系统,实现并不复杂,使用一个 HashMap,Map 的键

类型是 Key,值类型是 FileSystem。

Key 有三个属性:

6

张孟志, 13年8月11日,
本片开始的两段代码也使用了这句
Page 7: Hadoop源码分析(10HDFS外壳)

scheme: 该属性从 URI 中取得,比如一个 URI“http://server/index.html”,那

么 scheme 就是 http;

authority: 该 属性也 从 URI 中 取 得 , 在 上述的 例 子 中 , authority 就 是

server,authority 包括用户信息、主机以及端口;

ugi: 当 前 登 陆 的 用 户 , 具 体 细 节 可 见

org.apache.hadoop.security.UserGroupInformation。

由此可见,缓冲使用 scheme、authority 和 ugi 来标识文件系统。

FileSystem.Statistics

用于统计文件系统的情况,有六个属性(看属性名即可以猜出其作用),

其中:

scheme: 标识文件系统,像 HDFS 文件系统该属性就为 hdfs

bytesRead: 记录目前读取的字节数,类型为 AtomicLong,以避免数据不同

步问题

bytesWritten: 记录目前写入的字节数,类型为 AtomicLong,以避免数据不

同步问题7

Page 8: Hadoop源码分析(10HDFS外壳)

private final String scheme; private AtomicLong bytesRead = new AtomicLong(); private AtomicLong bytesWritten = new AtomicLong(); private AtomicInteger readOps = new AtomicInteger(); private AtomicInteger largeReadOps = new AtomicInteger(); private AtomicInteger writeOps = new AtomicInteger();

Path

用于描述文件或目录的路径,路径使用斜线(/)作为目录的分隔符,如果

一个路径以斜线开头则是绝对路径。主要是封装了 URI,增添一些检查和处理,

使该类能正确处理不同文件系统的路径和目录分隔符。

BlockLocation

用于保存文件中一个块的信息,这块和 HDFS 中的 Block 是一致的。看其

属性就能大概知道其用途: private String[] hosts; //hostnames of datanodes private String[] names; //hostname:portNumber of datanodes private String[] topologyPaths; // full path name in network topology private long offset; //offset of the of the block in the file private long length; //块的大小FileStatus

用于记录文件/目录的信息,记录的内容和 Unix、Linux 系统很像: private Path path; 文件/目录路径 private long length; 文件/目录大小 private boolean isdir; 是否是目录 private short block_replication; 块的副本数 private long blocksize; 块的大小

8

Page 9: Hadoop源码分析(10HDFS外壳)

private long modification_time; 文件/目录最后修改时间 private long access_time; 文件/目录最后访问时间 private FsPermission permission; 文件/目录的访问权限 private String owner; 文件/目录的所有者 private String group; 文件/目录所属的组FsPermission

用于控制文件/目录的访问权限,使用 POSIX 权限方式,控制用户、组、其

它的权限,权限有读、写、执行。

FSDataOutputStream

在文件系统中用于输出数据的流,继承 DataOutputStream,实现 Syncable

接口,因此,必须支持 sync操作。

这个类很简单,当实现一个具体的文件系统时,需要自己定制一个输出流,

实现特定的功能,只要继承自 FSDataOutputStream,就能符合 FileSystem 的接

口。

FSDataInputStream

在文件系统中用于输入数据的流,继承 DataInputStream,实现 Seekable 和

PositionReadable 接口,因此,必须支持 seek 和从某个位置开始读取的操作。

这个类也是很简单,当实现一个具体的文件系统时,需要自己定制一个输9

Page 10: Hadoop源码分析(10HDFS外壳)

入流,实现特定的功能,只要继承自 FSDataInputStream,就能符合 FileSystem

的接口。

上面对 FileSystem操作涉及的类进行了简要的介绍,但 FileSystem 是个抽

象类,具体是哪个类实现了“文件系统”呢?从 FileSystem.get 方法开始查看:/** Returns the FileSystem for this URI's scheme and authority. The scheme * of the URI determines a configuration property name, * <tt>fs.<i>scheme</i>.class</tt> whose value names the FileSystem class. * The entire URI is passed to the FileSystem instance's initialize method. */ public static FileSystem get(URI uri, Configuration conf) throws IOException { String scheme = uri.getScheme(); String authority = uri.getAuthority();

if (scheme == null) { // no scheme: use default FS return get(conf); }

if (authority == null) { // no authority URI defaultUri = getDefaultUri(conf); if (scheme.equals(defaultUri.getScheme()) // if scheme matches default && defaultUri.getAuthority() != null) { // & default has authority return get(defaultUri, conf); // return default } } String disableCacheName = String.format("fs.%s.impl.disable.cache", scheme); if (conf.getBoolean(disableCacheName, false)) { return createFileSystem(uri, conf); }

return CACHE.get(uri, conf); }

可以看到,通过查询缓存,如果没有相对应的 FileSystem 实例则创建一个。

10

Page 11: Hadoop源码分析(10HDFS外壳)

private static FileSystem createFileSystem(URI uri, Configuration conf ) throws IOException { Class<?> clazz = conf.getClass("fs." + uri.getScheme() + ".impl", null); LOG.debug("Creating filesystem for " + uri); if (clazz == null) { throw new IOException("No FileSystem for scheme: " + uri.getScheme()); } FileSystem fs = (FileSystem)ReflectionUtils.newInstance(clazz, conf); fs.initialize(uri, conf); return fs; }

创建 FileSystem 实例很简单,就是通过配置项获取相应的实现类。例如要

求获取 Scheme 为 hdfs 的 FileSystem 实例,就会查找配置项 fs.hdfs.impl,这个配

置项默认实现是“org.apache.hadoop.hdfs.DistributedFileSystem”,找到配置项后

就可以通过 JAVA 的反射机制创建实例。

DistributedFileSystem 是用于 DFS 系统的抽象文件系统的实现,继承自

FileSystem , 用 户 在 使 用 HDFS 时 , 所 使 用 的 文 件 系 统 就 是 该 实 现 。 但 是

DistributedFileSystem 的实现并不复杂,没有过多的逻辑,大部分方法会间接调

用 DFSClient 的方法,使 DFSClient 能兼容 Hadoop 的 FileSystem 接口,从而能

在 Hadoop 系统中工作。

来看看与DistributedFileSystem 相关的类图,由于涉及到的类繁多,因此只

列出关键类的属性和方法,其它的类只有类名:11

张孟志, 13年8月11日,
张孟志, 13年8月11日,
基本类似于Decorator、Adapter
张孟志, 13年8月11日,
Hadoop是一个综合性的文件系统抽象(FileSystem),Hadoop Distributed Filesystem, HDFS 是分布式系统的一个旗舰级实现。HDFS/hdfs/DistributedFileSystem之外,Hadoop还可以集成其他文件系统,如Local/file/LocalFileSystem、hfs/kfs/kosmosFileSystem、S3/s3n/NativeS3FileSystem
Page 12: Hadoop源码分析(10HDFS外壳)

从 上 图 可 以 看 出 依 赖 或 关 联 的 类 基 本 是 HDFS 中 通 用 的 类 和

org.apache.hadoop.fs 包下的与文件系统相关的类,DistributedFileSystem 的大部

分方法会调用 DFSClien 对应的方法,待下方分析DFSClient 时再进行介绍。

先来看看类的初始,在静态初始化块中加载了 hdfs-default.xml 和 hdfs-

site.xml 配置文件,其中包含了 namenode 的信息以及一些与 HDFS 相关的参数;

12

Page 13: Hadoop源码分析(10HDFS外壳)

在初始化对象时,从 uri 中得到 namenode 地址,设置默认工作目录为用户目录。

有三个方法频繁被其它方法调用:

checkPath,检查路径的 scheme、port 和 authority,允许显式指定默认端口

的路径;makeQualified,归一化显式指定默认端口的路径;getPathName,检查

路径的合法性,并将相对路径转换成绝对路径。

DFSClient 是一个真正实现了客户端功能的类,它能够连接到一个 Hadoop

文件系统并执行基本的文件任务。它使用 ClientProtocol 来和 NameNode 通信,

并且使用 Socket直接连接到 DataNode 来完成块数据的读/写。Hadoop 用户应该

得到一个 DistributedFileSystem 实例,该实现使用了 DFSClient 来处理文件系统

任务,而不是直接使用 DFSClient。

我们先来看看与 DFSClient 相关的类图,由于涉及到的类繁多,因此只列

出关键类的属性和方法,其它的类只有类名:

13

张孟志, 13年8月11日,
Page 14: Hadoop源码分析(10HDFS外壳)

先来看看 DFSClient 一些重要的属性:

MAX_BLOCK_ACQUIRE_FAILURES:块最大请求失败次数,值为 3;

TCP_WINDOW_SIZE:TCP窗口的大小,值为 128KB,在 seek操作中会

用到,假如目标位置在当前块内及在当前位置之后,并且与当前位置的距离不

超过 TCP_WINDOW_SIZE,那么这些数据很可能在 TCP 缓冲区中,只需要通

过读取操作来跳过这些数据;

14

Page 15: Hadoop源码分析(10HDFS外壳)

rpcNamenode:通过建立一个 RPC 代理来和 namenode 通信;

namenode:在 rcpNamenode 基础上封装了一个 Retry 代理,添加了一些

RetryPolicy;

leasechecker:租约管理,用于管理正被写入的文件输出流;

defaultBlockSize:块大小,默认是 64MB;

defaultReplication:副本数,默认是 3;

socketTimeout:socket超时时间,默认是 60秒;

datanodeWriteTimeout:datanode 写超时时间,默认是 480秒;

writePacketSize:写数据时,一个 packet 的大小,默认是 64KB;

maxBlockAcquireFailures:块最大请求失败次数,默认是 3,主要用于向

datanode请求块时,失败了可以重试。

DSClient 的属性主要是在初始化对象时设置,其中涉及到几个参数,如下

所示:

dfs.socket.timeout:读超时;

dfs.datanode.socket.write.timeout:写超时;15

Page 16: Hadoop源码分析(10HDFS外壳)

dfs.write.packet.size:一个的 packet 大小;

dfs.client.max.block.acquire.failures:块最大请求失败次数;

mapred.task.id : map reduce 的 ID , 如 果不为 空 , clientName 设 置 为

“DFSClient_”,否则 clientName 设置为“DFSClient_”;

dfs.block.size:块大小;

dfs.replication:副本数;

接下来,可以来看看 DFSClient 的方法,发现很多方法是通过 RPC 调

namenode 的方法,根据方法名都能看出要实现什么操作,下面着重说一下部分

方法:

checkOpen : 这 个 方 法 被频繁 调 用 , 但 过 程 很 简 单 ,只是 检 查 一 下

clientRunning 的值;

getBlockLocations:由于从 namenode 得到的所有块以 LocatedBlocks 来描述,

那么需要从 LocatedBlocks 从提取出每个块及拥有该块的 datanode 信息,并以

BlockLocation 来描述每个块,最后返回的是 BlockLocation 数组;

getFileChecksum:得到文件的 checksum,过程稍微复杂了一点:得到文件16

张孟志, 13年8月11日,
详细内容参考RPC、NameNode
Page 17: Hadoop源码分析(10HDFS外壳)

所有块的位置;对于每个块,向 datanode请求 checksum 信息,返回的信息中包

括块的所有 checksum 的 MD5摘要,如果向一个 datanode请求失败,会向另一

datanode请求;将所有块的 MD5合并,并计算这些内容的 MD5摘要;

bestNode:挑选一个不在 deadNodes 中的节点

17