天天看点

Hadoop-0.20.0源代码分析(03)

在Hadoop框架源代码org.apache.hadoop.fs包中,都是关于Hadoop文件系统实现的相关类,主要包括文件系统模型的建立,及其在该文件系统定义、实现基本的文件操作。例如给出文件系统抽象,对文件系统上存储的文件执行基本操作进行抽象,等等。

在该包中,类的继承关系如下所示:

◦java.lang.Object

◦org.apache.hadoop.fs.BlockLocation (implements org.apache.hadoop.io.Writable)

◦org.apache.hadoop.conf.Configured (implements org.apache.hadoop.conf.Configurable)

◦org.apache.hadoop.fs.FileSystem (implements java.io.Closeable)

◦org.apache.hadoop.fs.FilterFileSystem

◦org.apache.hadoop.fs.ChecksumFileSystem

◦org.apache.hadoop.fs.InMemoryFileSystem

◦org.apache.hadoop.fs.LocalFileSystem

◦org.apache.hadoop.fs.HarFileSystem

◦org.apache.hadoop.fs.RawLocalFileSystem

◦org.apache.hadoop.fs.FsShell (implements org.apache.hadoop.util.Tool)

◦org.apache.hadoop.fs.Trash

◦org.apache.hadoop.fs.ContentSummary (implements org.apache.hadoop.io.Writable)

◦org.apache.hadoop.fs.FileChecksum (implements org.apache.hadoop.io.Writable)

◦org.apache.hadoop.fs.MD5MD5CRC32FileChecksum

◦org.apache.hadoop.fs.FileStatus (implements java.lang.Comparable<T>, org.apache.hadoop.io.Writable)

◦org.apache.hadoop.fs.FileSystem.Statistics

◦org.apache.hadoop.fs.FileUtil

◦org.apache.hadoop.fs.FileUtil.HardLink

◦org.apache.hadoop.fs.FsUrlStreamHandlerFactory (implements java.net.URLStreamHandlerFactory)

◦java.io.InputStream (implements java.io.Closeable)

◦java.io.FilterInputStream

◦java.io.BufferedInputStream

◦org.apache.hadoop.fs.BufferedFSInputStream (implements org.apache.hadoop.fs.PositionedReadable, org.apache.hadoop.fs.Seekable)

◦java.io.DataInputStream (implements java.io.DataInput)

◦org.apache.hadoop.fs.FSDataInputStream (implements org.apache.hadoop.fs.PositionedReadable, org.apache.hadoop.fs.Seekable)

◦org.apache.hadoop.fs.FSInputStream (implements org.apache.hadoop.fs.PositionedReadable, org.apache.hadoop.fs.Seekable)

◦org.apache.hadoop.fs.FSInputChecker

◦org.apache.hadoop.fs.LocalDirAllocator

◦java.io.OutputStream (implements java.io.Closeable, java.io.Flushable)

◦java.io.FilterOutputStream

◦java.io.DataOutputStream (implements java.io.DataOutput)

◦org.apache.hadoop.fs.FSDataOutputStream (implements org.apache.hadoop.fs.Syncable)

◦org.apache.hadoop.fs.FSOutputSummer

◦org.apache.hadoop.fs.Path (implements java.lang.Comparable<T>)

◦org.apache.hadoop.util.Shell

◦org.apache.hadoop.fs.DF

◦org.apache.hadoop.fs.DU

◦java.lang.Throwable (implements java.io.Serializable)

◦java.lang.Error

◦org.apache.hadoop.fs.FSError

◦java.lang.Exception

◦java.io.IOException

◦org.apache.hadoop.fs.ChecksumException

首先对文件系统最顶层抽象类FileSystem进行源代码的阅读分析。

FileSystem抽象类继承自org.apache.hadoop.conf.Configured配置基类,实现了java.io.Closeable接口,通过这一点,可以了解到,FileSystem抽象类作为一个文件系统的抽象定义,它是可配置的,也就是说可以通过指定的配置文件中的一些配置项来描述一个文件系统,实际上,最重要的配置类是org.apache.hadoop.conf.Configuration,org.apache.hadoop.conf.Configured中定义的方法就是对org.apache.hadoop.conf.Configuration配置类进行设置或获取,满足一个基于org.apache.hadoop.conf.Configuration配置类的其它类的需要。

FileSystem抽象类定义了文件系统所具有的基本特征和基本操作。首先从该抽象类的属性定义来看,这些属性描述了文件系统的静态特性。该类中定义了如下属性:

private static final String FS_DEFAULT_NAME_KEY = "fs.default.name";

/** 文件系统缓存 */

private static final Cache CACHE = new Cache();

/** 该文件系统(this)在缓存中的键实例 */

private Cache.Key key;

/** 记录文件系统类的统计信息的Map */

private static final Map<Class<? extends FileSystem>, Statistics> statisticsTable = new IdentityHashMap<Class<? extends FileSystem>, Statistics>();

/**

* 该文件系统(this)的统计信息的实例

*/

protected Statistics statistics;

/**

* 当文件系统关闭或者JVM退出以后,需要将缓存中的文件清空。该Set<Path>中的内容是,对缓存中文件的Path,并且是排好序的。

*/

private Set<Path> deleteOnExit = new TreeSet<Path>();

Hadoop框架实现的文件系统,从FileSystem的Cache CACHE的含义可以看出,一个文件系统可以管理与它相关的并被缓存的多个文件系统的实例,这一组文件系统协调存储工作,并为Hadoop实现的MapReduce并行计算框架的机制提供便利的存储基础。

文件系统缓存

 FileSystem抽象类定义了一个文件系统缓存Cache CACHE,用来缓存文件系统对象。也就是可能存在多个文件系统对象,从而可知,每个文件系统除了管理基于其上的内容之外,还可能要管理缓存的一组文件系统实例,这要看具体的文件系统是如何实现的。

当然,也可能是在分布式环境中,一个文件系统管理远程的和本地的文件系统实例。

为了能够快速获取到一个存在于缓存中的文件系统对象,Hadoop采用了Hash算法,将文件系统对象以键值对的方式存储到HashMap中,也就是org.apache.hadoop.fs.FileSystem.Cache缓存类定义的map属性,如下所示:

private final Map<Key, FileSystem> map = new HashMap<Key, FileSystem>();

  其中,org.apache.hadoop.fs.FileSystem.Cache.Key是org.apache.hadoop.fs.FileSystem.Cache的一个内部静态类,作为缓存Cache中Map的键,一个Key所包含的内容就是一个URI的信息及其用户名,下面是Key类的属性:

final String scheme;

final String authority;

final String username;

缓存org.apache.hadoop.fs.FileSystem.Cache的Map的值是继承自FileSystem抽象类的子类。可以看出,可以通过一个合法的URI信息与用户名快速获取到缓存中存在的一个文件系统的对象,从而能够获取到指定文件系统中文件信息。该缓存类提供了3个基本的操作,如下所示:

/** 根据URI与Configuration,从缓存中取出一个FileSystem实例,要求同步缓存操作。 */

synchronized FileSystem get(URI uri, Configuration conf) throws IOException;

/** 根据指定的缓存Key实例,从缓存中删除该Key对应的FileSystem实例,要求同步缓存操作。 */

synchronized void remove(Key key, FileSystem fs);

/** 迭代缓存Map,删除缓存中的缓存的全部文件系统实例,要求同步缓存操作。 */

synchronized void closeAll() throws IOException;

文件系统统计信息

上面statisticsTable是一个IdentityHashMap<Class<? extends FileSystem>, Statistics>,键是继承自FileSystem的Class,值是统计信息Statistics类。为了在一个并行计算环境中进行安全的计算,Statistics类使用了java.util.concurrent.atomic包中的原子变量属性,保证线程安全的原子读写操作的同时,提高并行性能。如下所示:

private AtomicLong bytesRead = new AtomicLong();

private AtomicLong bytesWritten = new AtomicLong();

 其中,bytesRead是从统计数据中读取指定数量的字节,加到当前读取字节数上。同理,bytesRead是基于原子写操作的。

另外一个统计数据属性protected Statistics statistics,是对当前(this)的FileSystem的统计信息实例。该属性是在该文件系统(this)的实例被构造完成之后被初始化的,通过调用initialize方法实现统计信息初始化:

public void initialize(URI name, Configuration conf) throws IOException {

statistics = getStatistics(name.getScheme(), getClass());

}

 然后又在initialize方法内部调用了getStatistics方法获取到一个初始化的Statistics实例。在该方法中,在实例化一个Statistics实例以后,需要将它加入到统计信息实例的缓存statisticsTable中,以便能够通过给定的URI快速获取到对应的文件系统的统计信息。

为了便捷操作文件系统的统计信息,Filesystem类实现了几个非常方便的方法,下面只列出方法声明:

public static synchronized Map<String, Statistics> getStatistics();

public static synchronized List<Statistics> getAllStatistics();

public static synchronized Statistics getStatistics(String scheme, Class<? extends FileSystem> cls);

public static synchronized void clearStatistics();

public static synchronized void printStatistics() throws IOException;

这几个方法,都是从statisticsTable中获取到文件系统的统计信息。

文件缓存

属性Set<Path> deleteOnExit是一个文件缓存,它用来收集当前缓存中的文件Path。当文件系统关闭,或者JVM退出的时候,需要将缓存中的文件全部删除。删除缓存文件的方法是在processDeleteOnExit方法中,如下所示:

/**

* 删除缓存deleteOnExit中的全部文件,需要同步deleteOnExit。

*/

protected void processDeleteOnExit() {

synchronized (deleteOnExit) {

for (Iterator<Path> iter = deleteOnExit.iterator(); iter.hasNext();) {

Path path = iter.next();

try {

delete(path, true); // 调用,删除目录,及其子目录和文件

}

catch (IOException e) {

LOG.info("Ignoring failure to deleteOnExit for path " + path);

}

iter.remove();

}

}

}

当一个FileSystem关闭以后,需要将该文件系统对应的Path加入到文件缓存deleteOnExit中,以便在文件系统关闭或JVM退出时,调用processDeleteOnExit方法删除这些文件。向文件缓存中加入一个可能在文件系统关闭或JVM退出时删除的文件,在deleteOnExit方法中实现的。

文件系统抽象

下面,从FileSystem抽象类“抽象”的切面横向了解一个FileSystem定义了哪些基于文件系统的操作,使我们能够知道如果实现一个基于文件系统,需要实现哪些基本操作。如下所示,FileSystem抽象类中定义了12个抽象方法:

/** 获取能够唯一标识一个FileSystem的URI*/

public abstract URI getUri();

/**

* 根据给定的Path f,打开一个文件的FSDataInputStream输入流。

* @param f 待打开的文件

* @param bufferSize 缓冲区大小

*/

public abstract FSDataInputStream open(Path f, int bufferSize) throws IOException;

/**

* 为写入进程打开一个FSDataOutputStream。

* @param f 待写入的文件

* @param permission 权限

* @param overwrite 是否重写

* @param bufferSize 缓冲区大小

* @param replication 文件的块副本数量

* @param blockSize 块大小

* @param progress 用于报告Hadoop框架工作状况的进程

* @throws IOException

*/

public abstract FSDataOutputStream create(Path f,

FsPermission permission,

boolean overwrite,

int bufferSize,

short replication,

long blockSize,

Progressable progress) throws IOException;

/**

* 向一个已经存在的文件中执行追加操作

* @param f 存在的文件

* @param bufferSize 缓冲区大小

* @param progress 报告进程

* @throws IOException

*/

public abstract FSDataOutputStream append(Path f, int bufferSize, Progressable progress) throws IOException;

/**

* 重命名文件src为dst

*/

public abstract boolean rename(Path src, Path dst) throws IOException;

/**

* 删除文件

*/

public abstract boolean delete(Path f) throws IOException;

/**

* 删除文件

*/

public abstract boolean delete(Path f, boolean recursive) throws IOException;

/**

* 如果f是一个目录,列出该目录中的文件

*/

public abstract FileStatus[] listStatus(Path f) throws IOException;

/**

* 为给定的文件系统设置当前工作目录

*/

public abstract void setWorkingDirectory(Path new_dir);

/**

* 获取文件系统的当前工作目录

*/

public abstract Path getWorkingDirectory();

/**

* 创建一个目录f

*/

public abstract boolean mkdirs(Path f, FsPermission permission) throws IOException;

/**

* 获取与f对应的统计信息实例

*/

public abstract FileStatus getFileStatus(Path f) throws IOException;

上面这些抽象方法应该是一个文件系统应该具备的基本操作,可能根据不同的需要设计一个基于FileSystem抽象类的子类实现类,这个文件系统的实现中,对于某些操作的实现细节可能因为文件系统的特点而不全相同。因此,可以灵活设计你所需要的文件系统。

文件操作

在Filesystem文件系统上,与文件相关的操作很多,主要包括文件的创建、读写、重命名、拷贝、删除这几个基本操作。

文件的创建,包括目录的创建和非目录文件的创建,创建目录的方法如下:

public boolean mkdirs(Path f) throws IOException {

return mkdirs(f, FsPermission.getDefault());

}

public abstract boolean mkdirs(Path f, FsPermission permission) throws IOException;

Filesystem抽象类没有实现如何创建目录的细节。

另外,还有一个跨文件系统执行创建目录操作的实现:

public static boolean mkdirs(FileSystem fs, Path dir, FsPermission permission) throws IOException {

boolean result = fs.mkdirs(dir); // 基于默认权限创建一个目录,返回文件输出流对象

fs.setPermission(dir, permission); // 设置fs中创建dir目录的权限

return result;

}

通过这个方法可以看出,是在当前文件系统(this)中,在另一个文件系统fs中根据指定的权限来创建一个目录,显然这是在分布式地进行目录的远程创建操作。

对于非目录文件的创建,主要是为了读或写操作而打开一个文件,返回文件的流对象,可以进行流式读写与追加。对创建文件的操作,有10个重载的方法都是基于一个create抽象方法的:

public abstract FSDataOutputStream create(Path f,

FsPermission permission,

boolean overwrite,

int bufferSize,

short replication,

long blockSize,

Progressable progress) throws IOException;

还有一个比较特殊的create方法,如下所示:

public static FSDataOutputStream create(FileSystem fs,

Path file, FsPermission permission) throws IOException {

FSDataOutputStream out = fs.create(file); // 基于默认权限创建一个文件,返回文件输出流对象

fs.setPermission(file, permission); // 设置fs中创建file文件的权限

return out;

}

通过这个方法的参数可以看出,是在当前文件系统(this)中,在另一个文件系统fs中根据指定的权限来创建一个文件,显然这是在分布式地进行文件的远程创建操作。只要该文件系统的的权限满足远程文件系统fs的创建要求,并满足必要的通信条件,就可以执行分布式文件操作。

另外还有两个open方法是用来打开已经存在的文件而且返回文件流对象;一个createNewFile方法内部实现也是调用了create方法。

文件的追加操作,是通过三个重载的append方法实现的,追加写操作成功完成之后,返回org.apache.hadoop.fs.FSDataOutputStream流对象。

文件的重命名操作,是通过抽象方法rename(Path, Path)定义的。

文件的删除操作,是通过delete方法定义的。

本地文件的拷贝操作,主要是通过两组重载的方法实现的。一组是重载的copyFromLocalFile方法:拷贝源文件到目的文件,保留源文件(复制操作);另一组是重载的moveFromLocalFile方法:拷贝源文件到目的文件,删除源文件,这是文件的移动操作(就是剪切操作)。

文件、块、副本

关于文件和块,可以通过Hadoop的架构设计中了解到一些相关信息,一些参数的含义及其设置。

关于块(Block),FileSystem中定义了如下两个方法:

/**

* 获取文件f的块大小

*/

public long getBlockSize(Path f) throws IOException {

return getFileStatus(f).getBlockSize();

}

/**

* 获取默认块大小

*/

public long getDefaultBlockSize() {

// default to 32MB

return getConf().getLong("fs.local.block.size", 32 * 1024 * 1024);

}

为了保证Hadoop分布式文件系统的可靠性与可用性,使用了文件副本冗余存储和流水线复制技术。那么对于文件副本的设置也是有一定要求的。下面是关于副本的一些参数的操作:

/**

* 设置文件src的replication因子为replication

*/

public boolean setReplication(Path src, short replication) hrows IOException {

return true;

}

/**

* 获取文件src的replication因子

*/

@Deprecated

public short getReplication(Path src) throws IOException {

return getFileStatus(src).getReplication();

}

/**

* 获取文件的默认副本个数,亦即replication因子

*/

public short getDefaultReplication() { return 1; }

关于文件的状态信息,可以通过一组重载的listStatus方法来获取,文件状态信息通过org.apache.hadoop.fs.FileStatus实体类来统计,该类实现了org.apache.hadoop.io.Writable接口,因此是可序列化的。它主要包含文件的下述信息:

private Path path; // 文件路径

private long length; // 文件长度

private boolean isdir; // 是否是目录

private short block_replication; // 块副本因子

private long blocksize; // 块大小

private long modification_time; // 修改时间

private long access_time; // 访问时间

private FsPermission permission; //在指定文件系统中的操作权限

private String owner; // 文件属主

private String group; // 所属组

对于块,块是组成文件的基本单位,那么给定一个文件,它就应该具有一个块的列表,可以通过getFileBlockLocations方法获取到一个文件对应的块所在主机的列表、所在文件中的偏移位置等信息,如下:

/**

* 返回一个BlockLocation[],它包含了主机名列表、偏移位置、文件大小的信息

*/

public BlockLocation[] getFileBlockLocations(FileStatus file, long start, long len) throws IOException {

if (file == null) {

return null;

}

if ( (start<0) || (len < 0) ) {

throw new IllegalArgumentException("Invalid start or len parameter");

}

if (file.getLen() < start) {

return new BlockLocation[0];

}

String[] name = { "localhost:50010" };

String[] host = { "localhost" };

return new BlockLocation[] { new BlockLocation(name, host, 0, file.getLen()) };

}

其中,org.apache.hadoop.fs.BlockLocation类具有一个指定文件的块的信息,它实现了org.apache.hadoop.io.Writable接口,因此是可序列化的,它具有的信息如下所示:

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; // 块在文件中的偏移位置

private long length;

另外,Filesystem类中还定义了globStatus方法,用于根据指定的PathFilter来过滤文件系统中的文件Path,从而返回满足过滤条件的Path的文件状态信息的数组FileStatus[]。

继续阅读