HDFS Federation实践
  GQ7psP7UJw7k 2023年11月05日 58 0

1. 背景

在Hadoop2.0之前,一个Hadoop集群只支持一对主备NameNode。如下所示,集群中的数据接近2.2亿block,会导致NameNode内存中的文件系统树过大,占用较多内存;同时NameNode crash后启动时,由于需要加载过多的block,导致启动时间过长。

集群 每日写入block 每日净增block 每日数据净增长 当前block数 空间使用率
集群A 500-600W 60W-70W 100TB+ 2.2亿 71.93%

最简单的解决方法就是通过distcp工具将该集群中的项目迁移到另一个空闲的集群,但是ditscp工具有自身的缺陷:迁移过程耗时较长,且迁移过程中,禁止用户写入数据,对用户不友好。

为了解决HDFS水平扩展问题,https://issues.apache.org/jira/browse/HDFS-1052提供了HDFS Federation联邦机制。一个HDFS集群中,可以部署多对Active/Standby NameNode,每对Active/Standby NameNode可以称为子集群,它们各自存储自己的元数据,所有子集群都共用所有datanode。

这样,当一个子集群的block数接近3亿时,可以水平扩展一个新的子集群,使用该block压力较小的子集群存储HDFS文件。

2. HDFS Federation介绍

每个联邦集群中有多个子集群,每个子集群有对应一个namespace。下图中,有n个子集群,它们的namespace分别为NS1、NSk、NSn。每个子集群的block都会存储到集群中所有的datanode中,因此不同的子集群,会设置一个Block Pool ID,datanode根据Block Pool ID存储对应的block,上报块时,也会根据Block Pool ID找到对应的子集群,向指定NameNode进行汇报。

Untitled.png

例如,有一个HDFS联邦集群,它包含四个子集群,则集群中会有4个Block Pool,在datanode中,会保存4个Block Pool目录,分别保存4个子集群的block数据:

Untitled 1.png

因此,在表示block块时,要通过<BlockPoolID><BlockID>指定是哪一个BlockPool中的块。

在源码中也可以看到Federation相关逻辑。如下,Federation中定义BPOfferService类,它负责向单个子集群汇报块信息。datanode接收客户端请求刷新NameNodes时,如果有新增的NameNode节点,就会新建一个该子集群对应的BPOfferService,用户向该子集群汇报块信息:

private void doRefreshNamenodes(
      Map<String, Map<String, InetSocketAddress>> addrMap,
      Map<String, Map<String, InetSocketAddress>> lifelineAddrMap)
      throws IOException {
    Set<String> toRefresh = Sets.newLinkedHashSet();
    Set<String> toAdd = Sets.newLinkedHashSet();
    Set<String> toRemove;
    
    //省略
      if (!toAdd.isEmpty()) {
        LOG.info("Starting BPOfferServices for nameservices: " +
            Joiner.on(",").useForNull("<default>").join(toAdd));
      
        for (String nsToAdd : toAdd) {
          //省略
          BPOfferService bpos = createBPOS(nsToAdd, addrs, lifelineAddrs);
          bpByNameserviceId.put(nsToAdd, bpos);
          offerServices.add(bpos);
        }
      }
      startAll();
    }

3. ViewFS集成

虽然HDFS Federation支持子集群扩展,但是在用户视角,每个子集群都要都要指定其集群名,增加用户的记忆成本。为了降低使用成本,Hadoop集成ViewFS映射文件系统,在客户端中,可以设置一个路由表mountTable.xml文件,设置ViewFS中的路径与子文件系统中的路径对应关系。例如:

<configuration>
  <property>
    <name>fs.viewfs.mounttable.test1.homedir</name>
    <value>/test1</value>
  </property>
  <property>
    <name>fs.viewfs.mounttable.test1.link./user</name>
    <value>hdfs://test1/user</value>
  </property>
  <property>
    <name>fs.viewfs.mounttable.test1.link./data</name>
    <value>hdfs://test1/data</value>
  </property>
  <property>
    <name>fs.viewfs.mounttable.test1.link./src</name>
    <value>hdfs://test1/src</value>
  </property>
</configuration>

再在core-site.xml中设置viewfs默认URL:

<property>
    <name>fs.defaultFS</name>
    <value>viewfs://test1</value>
  </property>

<property>
    <name>fs.AbstractFileSystem.viewfs.impl</name>
    <value>org.apache.hadoop.fs.viewfs.ViewFs</value>
</property>

这样,访问viewfs://test1/user时,底层就会访问hdfs://test1/user。其他挂载路径同理。当然,viewfs底层还可以挂载其他文件系统,这里不展开。

观察ViewFS源码,可以看到在InodeTree()方法中初始化文件系统:

InodeTree<AbstractFileSystem> fsState;

ViewFs(final URI theUri, final Configuration conf) throws IOException,
      URISyntaxException {
    super(theUri, FsConstants.VIEWFS_SCHEME, false, -1);
    creationTime = Time.now();
    ugi = UserGroupInformation.getCurrentUser();
    config = conf;
    // Now build  client side view (i.e. client side mount table) from config.
    String authority = theUri.getAuthority();
    //调用InodeTree的构造函数创建文件系统
    fsState = new InodeTree<AbstractFileSystem>(conf, authority) {

      @Override
      protected AbstractFileSystem getTargetFileSystem(final URI uri)
        throws URISyntaxException, UnsupportedFileSystemException {
          String pathString = uri.getPath();
          if (pathString.isEmpty()) {
            pathString = "/";
          }
          return new ChRootedFs(
              AbstractFileSystem.createFileSystem(uri, config),
              new Path(pathString));
      }
   //省略
  }

在InodeTree中,读取mountTable.xml中的挂载点信息,创建对应的文件系统客户端对象:

protected InodeTree(final Configuration config, final String viewName)
      throws UnsupportedFileSystemException, URISyntaxException,
      FileAlreadyExistsException, IOException {
    String mountTableName = viewName;
    if (mountTableName == null) {
      mountTableName = Constants.CONFIG_VIEWFS_DEFAULT_MOUNT_TABLE;
    }
    homedirPrefix = ConfigUtil.getHomeDirValue(config, mountTableName);

    boolean isMergeSlashConfigured = false;
    String mergeSlashTarget = null;
    List<LinkEntry> linkEntries = new LinkedList<>();

    final String mountTablePrefix =
        Constants.CONFIG_VIEWFS_PREFIX + "." + mountTableName + ".";
    final String linkPrefix = Constants.CONFIG_VIEWFS_LINK + ".";
    final String linkFallbackPrefix = Constants.CONFIG_VIEWFS_LINK_FALLBACK;
    final String linkMergePrefix = Constants.CONFIG_VIEWFS_LINK_MERGE + ".";
    final String linkMergeSlashPrefix =
        Constants.CONFIG_VIEWFS_LINK_MERGE_SLASH;
    boolean gotMountTableEntry = false;
    final UserGroupInformation ugi = UserGroupInformation.getCurrentUser();
    //读取配置中的每个挂载点
    for (Entry<String, String> si : config) {
      final String key = si.getKey();
      if (key.startsWith(mountTablePrefix)) {
        gotMountTableEntry = true;
        LinkType linkType;
        String src = key.substring(mountTablePrefix.length());
        String settings = null;
        if (src.startsWith(linkPrefix)) {
          src = src.substring(linkPrefix.length());
          //省略
          //每个挂载点都进行记录
          linkEntries.add(
              new LinkEntry(src, target, linkType, settings, ugi, config));
        //省略
        //创建对应的文件系统客户端对象
      for (LinkEntry le : linkEntries) {
        //省略
          fallbackLink = new INodeLink<T>(mountTableName, ugi,
              getTargetFileSystem(new URI(le.getTarget())),
              new URI(le.getTarget()));
        } else {
          createLink(le.getSrc(), le.getTarget(), le.getLinkType(),
              le.getSettings(), le.getUgi(), le.getConfig());
        }
      }
      rootFallbackLink = fallbackLink;
    }
//省略
  }

经过ViewFS测试,得到以下ViewFS使用要求:

  1. 表和分区的schem需要保持一致,统一设置为hdfs或者viewfs。
  2. 表和分区需要在同一个集群,配置viewfs的情况下,可以跨NS。
  3. Spark SQL测试和HIVE表现基本一致,HIVE检查表和分区的schem更严格。

4. ViewFS Based Federation弊端

由于挂载表记录在每个客户端的mountTable.xml中,有以下弊端:

  1. 用户误操作,修改了客户端挂载配置,可能导致集群数据误删等高危操作。
  2. 维护困难。如果数据在子集群间进行迁移,那么所有客户端的配置都需要变更。有些用户的客户端配置不在变更范围内,就会导致访问的数据不符预期。
【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
GQ7psP7UJw7k
最新推荐 更多

2024-05-03