Hadoop源码分析之FileSystem

新建了一个Configuration对象之后,在调用Configuration.get()获取配置键值对时,如果Configuration对象的properities为null,就会默认加载CLASSPATH上的默认配置文件(参见 Hadoop源码分析之Configuration),所以在得到一个Configuration对象之后就可以利用这个对象来新建一个FileSystem对象。

Hadoop抽象文件系统

为了为不同的文件系统提供一个统一的接口,Hadoop提供了一个抽象的文件系统,而Hadoop分布式文件系统(Hadoop Distributed File System, HDFS)只是这个抽象文件系统的一个具体实现。Hadoop抽象文件系统接口主要由抽象类org.apache.hadoop.fs.FileSystem提供,其继承的层次结构如下图:

Hadoop源码分析之FileSystem_第1张图片

从上图可以看出,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对象

下面是关于客户端获取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架构设计与实现原理》

你可能感兴趣的:(Hadoop源码分析之FileSystem)