新建了一个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, * <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();//获得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<FileSystem>() { 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文件中有这么一个配置:
<property> <name>fs.default.name</name> <value>hdfs://localhost:9000</value> </property> <property>而常量 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<Key, FileSystem> map = new HashMap<Key, FileSystem>();这个键值对映射的键是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代码如下:
<property> <name>fs.hdfs.impl</name> <value>org.apache.hadoop.hdfs.DistributedFileSystem</value> <description>The FileSystem for hdfs: uris.</description> </property>
所以若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架构设计与实现原理》