Scheme覆盖式的ViewFileSystem设计实现

前言


在多HDFS集群模式中,我们为了使得多集群对于client端的透明使用,一般可以采用的是ViewFs的方案。当然后来社区实现的HDFS RBF功能无疑是更佳的选择,但是在RBF出现,ViewFs实现的更早且方案更为简单,因此ViewFs是通过在client端实现的一个请求解析以及转发。但是本文我们来讨论一个ViewFs使用的痛点问题:ViewFs高成本的配置更新问题以及更为tricky的hdfs路径写死 metadata和应用程序里的问题。前者我们可以通过中心化统一viewfs配置的方式解决,而本文我们来主题聊聊后面这个难题。这里提到的写死hdfs路径在应用服务中的情况,它实质所要表达的意思是:如何在不改变现有程序中的路径的情况下,server端还是能够自动帮我重定向到新的target路径上去。本文笔者来聊聊目前社区对此的功能实现。

Scheme覆盖式的ViewFileSystem


我们再来用一个简单的例子来阐述下上面提到的例子。

我们现有以下写死的配置,路径指向关系如下:

hdfs://clusterA/a —> hdfs://clusterA/a
hdfs://clusterB/b —> hdfs://clusterB/b

现在我们突然有个临时需求,需要将写入到hdfs://clusterA/a的数据能够转向写到hdfs://clusterB/a上去。但是此时这些hdfs路径都是写死的,如果按照老HDFS FileSystem访问方式,它将还是按照地址直接访问clusterA集群的路径来的。

因此,在这里我们需要一个能够带有覆盖scheme方式的FileSystem,它的新映射关系如下:

hdfs://clusterA/a —> hdfs://clusterB/b
hdfs://clusterB/b —> hdfs://clusterB/b

这种模式我们可以扩展在现有的VIewFileSystem模式下,效果图如下所示:
在这里插入图片描述
如上图所示,scheme覆盖式的FileSystem叫做ViewFileSystemOverloadScheme。它和原来ViewFileSystem最大的区别是它可以在非viewfs scheme的路径((比如原始hdfs路径)下做到原来Viewfs的多路径mount table mapping的功能。这点带来的改进就可以很好的解决,那些hard-code hdfs路径导致访问地址无法更新的问题。

但是在ViewFileSystemOverloadScheme中,上面defaultFs路径写的hdfs://xxx,就不再指的是实际的DistributedFileSystem实例了,而是ViewFileSystemOverloadScheme。

OK,下面我们来进一步探讨ViewFileSystemOverloadScheme如何做到覆盖式scheme的处理的。

ViewFileSystemOverloadScheme的实现


使用ViewFileSystemOverloadScheme的第一步,先把defaultFs指向的fs.impl给替换掉。

替换规则如下

<property>
  <name>fs.<scheme>.impl</name>
  <value>org.apache.hadoop.fs.viewfs.ViewFileSystemOverloadScheme</value>
</property>

如果原有defaultFs的scheme是hdfs的话,则进行如下替换

<property>
  <name>fs.hdfs.impl</name>
  <value>org.apache.hadoop.fs.viewfs.ViewFileSystemOverloadScheme</value>
</property>

这样的话,程序在解析hdfs://xxx路径时就会总向ViewFileSystemOverloadScheme的逻辑。

对应代码逻辑如下,FileSystem类:

  private static FileSystem createFileSystem(URI uri, Configuration conf)
      throws IOException {
    
    
    Tracer tracer = FsTracer.get(conf);
    try(TraceScope scope = tracer.newScope("FileSystem#createFileSystem")) {
    
    
      scope.addKVAnnotation("scheme", uri.getScheme());
      // 关键获取FileSystem class 方法
      Class<? extends FileSystem> clazz =
          getFileSystemClass(uri.getScheme(), conf);
          ...
  }

  public static Class<? extends FileSystem> getFileSystemClass(String scheme,
      Configuration conf) throws IOException {
    
    
    if (!FILE_SYSTEMS_LOADED) {
    
    
      loadFileSystems();
    }
    LOGGER.debug("Looking for FS supporting {}", scheme);
    Class<? extends FileSystem> clazz = null;
    if (conf != null) {
    
    
      // fs.<scheme>.impl需要额外指定,否则取后面SERVICE_FILE_SYSTEMS内scheme对应的FileSystem
      String property = "fs." + scheme + ".impl";
      LOGGER.debug("looking for configuration option {}", property);
      clazz = (Class<? extends FileSystem>) conf.getClass(
          property, null);
    } else {
    
    
      LOGGER.debug("No configuration: skipping check for fs.{}.impl", scheme);
    }
    if (clazz == null) {
    
    
      LOGGER.debug("Looking in service filesystems for implementation class");
      clazz = SERVICE_FILE_SYSTEMS.get(scheme);
    } else {
    
    
      LOGGER.debug("Filesystem {} defined in configuration option", scheme);
    }
    if (clazz == null) {
    
    
      throw new UnsupportedFileSystemException("No FileSystem for scheme "
          + "\"" + scheme + "\"");
    }
    LOGGER.debug("FS for {} is {}", scheme, clazz);
    return clazz;
  }


接着在FileSystem的get方法后,就会进行入ViewFileSystemOverloadScheme的initialize方法内,

public class ViewFileSystemOverloadScheme extends ViewFileSystem {
    
    
  private URI myUri;
  ...
  @Override
  public void initialize(URI theUri, Configuration conf) throws IOException {
    
    
    this.myUri = theUri;
    if (LOG.isDebugEnabled()) {
    
    
      LOG.debug("Initializing the ViewFileSystemOverloadScheme with the uri: "
          + theUri);
    }
    String mountTableConfigPath =
        conf.get(Constants.CONFIG_VIEWFS_MOUNTTABLE_PATH);
    /* The default value to false in ViewFSOverloadScheme */
    conf.setBoolean(Constants.CONFIG_VIEWFS_MOUNT_LINKS_AS_SYMLINKS,
        conf.getBoolean(Constants.CONFIG_VIEWFS_MOUNT_LINKS_AS_SYMLINKS,
            false));
    /* the default value to true in ViewFSOverloadScheme */
    conf.setBoolean(CONFIG_VIEWFS_IGNORE_PORT_IN_MOUNT_TABLE_NAME,
        conf.getBoolean(Constants.CONFIG_VIEWFS_IGNORE_PORT_IN_MOUNT_TABLE_NAME,
            true));
    if (null != mountTableConfigPath) {
    
    
      MountTableConfigLoader loader = new HCFSMountTableConfigLoader();
      loader.load(mountTableConfigPath, conf);
    } else {
    
    
      // TODO: Should we fail here.?
      if (LOG.isDebugEnabled()) {
    
    
        LOG.debug(
            "Missing configuration for fs.viewfs.mounttable.path. Proceeding"
                + "with core-site.xml mount-table information if avaialable.");
      }
    }
    // 进入父类ViewFileSystem的初始化方法,会进行mount table路径的file system实例初始化
    super.initialize(theUri, conf);
  }

在父类ViewFileSystem的初始化方法里,会进行mount table link的解析并初始化对应的FileSystem实例。

其中在这个过程中,会调用ViewFileSystemOverloadScheme#ChildFsGetter的类方法进行FileSystem的获取。

  /**
   * This class checks whether the rooScheme is same as URI scheme. If both are
   * same, then it will initialize file systems by using the configured
   * fs.viewfs.overload.scheme.target.<scheme>.impl class.
   */
  static class ChildFsGetter extends FsGetter {
    
    

    private final String rootScheme;

    ChildFsGetter(String rootScheme) {
    
    
      this.rootScheme = rootScheme;
    }

    @Override
    public FileSystem getNewInstance(URI uri, Configuration conf)
        throws IOException {
    
    
      // 如果uri schema和ViewFileSystemOverloadScheme scheme一致,为避免循环解析,
      // 直接创建对应的指明的target FileSystem,由配置项fs.viewfs.overload.scheme.target.%s.impl所决定
      if (uri.getScheme().equals(this.rootScheme)) {
    
    
        if (LOG.isDebugEnabled()) {
    
    
          LOG.debug(
              "The file system initialized uri scheme is matching with the "
                  + "given target uri scheme. The target uri is: " + uri);
        }
        /*
         * Avoid looping when target fs scheme is matching to overloaded scheme.
         */
        return createFileSystem(uri, conf);
      } else {
    
    
        // 其它情况可根据正常的FileSystem.newInstance获取对应正确的FileSystem实例
        return FileSystem.newInstance(uri, conf);
      }
    }
    ...
}

fs.viewfs.overload.scheme.target.%s.impl样例配置如下:

<property>
  <name>fs.viewfs.overload.scheme.target.hdfs.impl</name>
  <value>org.apache.hadoop.hdfs.DistributedFileSystem</value>
  <description>The DistributedFileSystem for view file system overload scheme
   when child file system and ViewFSOverloadScheme's schemes are hdfs.
   </description>
</property>

<property>
  <name>fs.viewfs.overload.scheme.target.s3a.impl</name>
  <value>org.apache.hadoop.fs.s3a.S3AFileSystem</value>
  <description>The S3AFileSystem for view file system overload scheme when
   child file system and ViewFSOverloadScheme's schemes are s3a.</description>
</property>

这里的调用关系如下图所示:
在这里插入图片描述

以上步骤执行完毕后,后续请求的路径访问就和原来ViewFileSystem并没有太多差别了。ViewFileSystemOverloadScheme继续沿用了内部已经解析好的映射关系做路径的访问。简单概括ViewFileSystemOverloadScheme的改进在于巧妙的将原路径透明地转化到了ViewFs的模式里,但是为了避免路径解析死循环,要指明被重载scheme路径的target FileSystem类型。

OK,以上就是本文所要阐述的全部内容,详细功能使用配置以及代码实现可阅读文末的链接处。

引用


[1].https://aajisaka.github.io/hadoop-document/hadoop-project/hadoop-project-dist/hadoop-hdfs/ViewFsOverloadScheme.html
[2].https://issues.apache.org/jira/browse/HDFS-15305

猜你喜欢

转载自blog.csdn.net/Androidlushangderen/article/details/107746645
今日推荐