新建了一个Configuration对象之后,在调用Configuration.get()获取配置键值对时,如果Configuration对象的properities为null,就会默认加载CLASSPATH上的默认配置文件(参见 Hadoop源码分析之Configuration),所以在得到一个Configuration对象之后就可以利用这个对象来新建一个FileSystem对象。
为了为不同的文件系统提供一个统一的接口,Hadoop提供了一个抽象的文件系统,而Hadoop分布式文件系统(Hadoop Distributed File System, HDFS)只是这个抽象文件系统的一个具体实现。Hadoop抽象文件系统接口主要由抽象类org.apache.hadoop.fs.FileSystem提供,其继承的层次结构如下图:
从上图可以看出,Hadoop发行包中包含了不同的FileSystem子类,以满足不同得到数据访问需求。比较典型的是访问HDFS上的数据,但有些时候也可访问存储在其他文件系统上,如Amazon的S3系统。此外,用户还可以根据特定的需求,自己实现特定网络存粗服务的具体文件系统。
Hadoop抽象文件系统为用户提供了一个访问不同的文件系统的统一接口,大部分接口都在FileSystem这个抽象类中。其主要提供的方法可以分为两部分:一部分用于处理文件和目录相关的事务;另一部分用于读写文件数据。其中处理文件和目录主要是指创建文件,创建目录,删除文件,删除目录等操作,读写数据文件主要是指读文件数据,写入文件数据等操作。这一些列操作大部分与Java的文件系统接口相似,如FileSystem.mkdirs(Path f, FsPermission permission)方法在FileSystem对象所代表的文件系统中创建目录,Java.io.File.mkdirs()也是创建目录的方法。FileSystem.delete(Path f)方法用于删除文件或目录,Java.io.File.delete()方法也用于删除文件或目录。FileSystem中需要子类实现的抽象方法大部分都是见名知意的方法,下面整理了FileSystem中的抽象方法:
/**获取文件系统URI**/
public abstract URI getUri();
/**打开一个文件,并返回一个输入流**/
public abstract FSDataInputStream open(Path f, int bufferSize)
throws IOException;
/**创建一个文件,并返回一个输入流**/
public abstract FSDataOutputStream create(Path f,
FsPermission permission,
boolean overwrite,
int bufferSize,
short replication,
long blockSize,
Progressable progress) throws IOException;
/**在一个已经存在的文件中追加数据**/
public abstract FSDataOutputStream append(Path f, int bufferSize,
Progressable progress) throws IOException;
/**修改文件名或目录名**/
public abstract boolean rename(Path src, Path dst) throws IOException;
/**删除文件**/
public abstract boolean delete(Path f, boolean recursive) throws IOException;
/**如果Path是一个目录,读取一个目录下的所有项目和项目属性,如果Path是一个文件获取该文件的属性**/
public abstract FileStatus[] listStatus(Path f) throws IOException;
/**设置当前的工作目录**/
public abstract void setWorkingDirectory(Path new_dir);
/**获取当前的工作目录**/
public abstract Path getWorkingDirectory();
/**创建文件夹**/
public abstract boolean mkdirs(Path f, FsPermission permission
) throws IOException;
/**获取文件或目录属性**/
public abstract FileStatus getFileStatus(Path f) throws IOException;
上面的方法基本上覆盖了Hadoop抽象文件系统具体实现所需要实现的方法。此外,Hadoop抽象文件系统基于以上方法,提供了一些工具方法,方便用户调用。如listStatus()方法等。
下面是关于客户端获取FileSystem的DistributedFileSystem对象
客户端要构造FileSystem对象可以使用FileSystem.get()方法,该方法有3个重载方法,分别是
/** Returns the configured filesystem implementation.
* 获取具体文件系统
* */
public static FileSystem get(Configuration conf) throws IOException {
return get(getDefaultUri(conf), conf);
}
/** Returns the FileSystem for this URI's scheme and authority. The scheme
* of the URI determines a configuration property name,
* fs.scheme.class 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();//获得URI的模式
String authority = uri.getAuthority();//鉴权信息
//URI模式为空,并且鉴权信息为空,返回默认文件系统
if (scheme == null && authority == null) { // use default FS
return get(conf);
}
//鉴权信息为空
if (scheme != null && 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)) {//是否使用被Cache的文件系统
return createFileSystem(uri, conf);
}
return CACHE.get(uri, conf);
}
public static FileSystem get(final URI uri, final Configuration conf,
final String user)
throws IOException, InterruptedException {
UserGroupInformation ugi;
if (user == null) {
ugi = UserGroupInformation.getCurrentUser();
} else {
ugi = UserGroupInformation.createRemoteUser(user);
}
return ugi.doAs(new PrivilegedExceptionAction() {
public FileSystem run() throws IOException {
return get(uri, conf);
}
});
}
但是都是调用有两个形参的FileSystem.get()方法获取FileSystem对象 。
在Hadoop源码分析之开篇给出的代码中获取FileSystem对象的那行代码是
FileSystem in = FileSystem.get(conf);
其中conf是一个Configuration对象。执行这行代码后就进入到FileSystem.get(Configuration conf)方法中,可以看到,在这个方法中先通过getDefaultUri()方法获取文件系统对应的的URI,该URI保存了与文件系统对应的协议和授权信息,如:
hdfs://localhost:9000。这个URI又是如何得到的呢?是在CLASSPATH中的配置文件中取得的,看
getDefaultUri()方法中有
conf.get(FS_DEFAULT_NAME_KEY, "file:///") 这么一个实参,在笔者项目的CLASSPATH中的core-site.xml文件中有这么一个配置:
fs.default.name
hdfs://localhost:9000
而常量
FS_DEFAULT_NAME_KEY对应的值是
fs.default.name,所以
conf.get(FS_DEFAULT_NAME_KEY, "file:///")得到的值是
hdfs://localhost:9000。
URI创建完成之后就进入到FileSystem.get(final URI uri, final Configuration conf)方法。在这个方法中,先执行一些检查,检查URI的协议和授权信息是否为空,然后再直接或简介调用该方法,最重要的是执行
String disableCacheName = String.format("fs.%s.impl.disable.cache", scheme);
if (conf.getBoolean(disableCacheName, false)) {//是否使用被Cache的文件系统
return createFileSystem(uri, conf);
}
return CACHE.get(uri, conf);
常量CACHE用于缓存已经打开的、可共享的文件系统,它是FileSystem类的静态内部类FileSystem.Cache的对象,在其内部使用一个Map存储文件系统
private final Map map = new HashMap();
这个键值对映射的键是FileSystem.Cache.Key类型,它有三个成员变量:
/**URI模式**/
final String scheme;
/**URI的授权部分**/
final String authority;
/**保存了打开具体文件系统的本地用户信息,不同本地用户打开的具体文件系统也是不能共享的**/
final UserGroupInformation ugi;
由于FileSystem.Cache表示可共享的文件系统,所以这个Key就用于区别不同的文件系统对象,如一个一个文件系统对象可共享,那么FileSystem.Cache.Key的三个成员变量相等,在这个类中重写了hashCode()方法和equals()方法,就是用于判断这三个变量是否相等。根据《Hadoop技术内幕:深入解析Hadoop Common和HDFS架构设计与实现原理》这本书的介绍,在Hadoop1。0版本中FileSystem.Cache.Key类还有一个unique字段,这个字段表示,如果其他3个字段相等的情况,下如果用户不想共享这个文件系统,就设置这个值(默认为0),但是不知道现在为什么去除了,还没搞清楚,有哪位同学知道的话麻烦告知,谢谢。
回到FileSystem.get(final URI uri, final Configuration conf)方法的最后一行语句return CACHE.get(uri, conf),调用了FileSystem.Cahce.get()方法获取具体的文件系统对象,该方法代码如下:
FileSystem get(URI uri, Configuration conf) throws IOException{
Key key = new Key(uri, conf);
FileSystem fs = null;
synchronized (this) {
fs = map.get(key);
}
if (fs != null) {
return fs;
}
fs = createFileSystem(uri, conf);
synchronized (this) { // refetch the lock again
FileSystem oldfs = map.get(key);
if (oldfs != null) { // a file system is created while lock is releasing
fs.close(); // close the new file system
return oldfs; // return the old file system
}
// now insert the new file system into the map
if (map.isEmpty() && !clientFinalizer.isAlive()) {
Runtime.getRuntime().addShutdownHook(clientFinalizer);
}
fs.key = key;
map.put(key, fs);
return fs;
}
}
在这个方法中先查看已经map中是否已经缓存了要获取的文件系统对象,如果已经有了,直接从集合中去除,如果没有才进行创建,由于FileSystem.CACHE为static类型,所以在同一时刻可能有多个线程在访问,所以需要在Cache类的方法中使用同步的操作来取值和设置值。这个方法比较简单,最核心的就是
fs = createFileSystem(uri, conf);
这行语句,它执行了具体的文件系统对象的创建的功能。createFileSystem()方法是FileSystem的一个私有方法,其代码如下:
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;
}
其实现就是先从配置文件取得URI对应的类,如在core-default.xml文件中属性(键)
fs.hdfs.impl对应的值是
org.apache.hadoop.hdfs.DistributedFileSystem,相应的XML代码如下:
fs.hdfs.impl
org.apache.hadoop.hdfs.DistributedFileSystem
The FileSystem for hdfs: uris.
所以若uri对应fs.hdfs.impl,那么createFileSystem中的clazz就是org.apache.hadoop.hdfs.DistributedFileSystem的Class对象。然后再利用反射,创建org.apache.hadoop.hdfs.DistributedFileSystem的对象fs。然后执行fs.initialize(uri, conf);初始化fs对象。DistributedFileSystem是Hadoop分布式文件系统的实现类,实现了Hadoop文件系统的界面,提供了处理HDFS文件和目录的相关事务。
执行DistributedFileSystem.initialize()方法之后,FileSystem对象就创建成功。
Reference:
《Hadoop技术内幕:深入解析Hadoop Common和HDFS架构设计与实现原理》