ViewFs 指南

简介

视图文件系统(ViewFs)提供了一种管理多个Hadoop文件系统命名空间(或命名空间卷)的方式。对于在HDFS联邦中拥有多个名称节点(因此具有多个命名空间)的集群特别有用。ViewFs类似于某些Unix/Linux系统中的客户端挂载表。ViewFs可用于创建个性化的命名空间视图,也可用于创建每个集群的公共视图。

本指南针对具有多个集群的Hadoop系统环境编写,每个集群可能被联合为多个命名空间。它还描述了如何在联合HDFS中使用ViewFs来为每个集群提供全局命名空间,使应用程序能够以类似于联合前世界的方式运行。

旧世界(联邦化之前)

单Namenode集群

HDFS Federation出现之前的旧世界中,一个集群只有一个namenode,为该集群提供单一的文件系统命名空间。假设存在多个集群,每个集群的文件系统命名空间完全独立且互不相交。此外,物理存储不会在集群之间共享(即Datanodes不会跨集群共享)。

每个集群的core-site.xml都有一个配置属性,用于将默认文件系统设置为该集群的namenode:

<property>
  <name>fs.default.name</name>
  <value>hdfs://namenodeOfClusterX:port</value>
</property>

这样的配置属性允许使用斜杠相对名称来解析相对于集群名称节点的路径。例如,路径/foo/bar通过上述配置指向hdfs://namenodeOfClusterX:port/foo/bar

此配置属性在集群的每个网关上设置,也在该集群的关键服务(如JobTracker和Oozie)上设置。

路径名使用模式

因此在集群X上,当core-site.xml按上述设置时,典型的路径名是

  1. /foo/bar

    • 这等同于之前的 hdfs://namenodeOfClusterX:port/foo/bar
  2. hdfs://namenodeOfClusterX:port/foo/bar

    • 虽然这是一个有效的路径名,但最好使用/foo/bar,因为这允许在需要时将应用程序及其数据透明地移动到另一个集群。
  3. hdfs://namenodeOfClusterY:port/foo/bar

    • 这是一个用于引用其他集群(如集群Y)上路径名的URI。具体来说,将文件从集群Y复制到集群Z的命令如下:
      distcp hdfs://namenodeClusterY:port/pathSrc hdfs://namenodeClusterZ:port/pathDest
      
  4. webhdfs://namenodeClusterX:http_port/foo/bar

    • 这是一个通过WebHDFS文件系统访问文件的URI。请注意WebHDFS使用namenode的HTTP端口而非RPC端口。
  5. http://namenodeClusterX:http_port/webhdfs/v1/foo/barhttp://proxyClusterX:http_port/foo/bar

路径名使用最佳实践

在集群内部时,建议使用上述类型(1)的路径名,而非类似(2)的完整限定URI。完整限定URI类似于地址,会限制应用程序随数据迁移的能力。

新世界 - 联邦与ViewFs

集群概览

假设存在多个集群。每个集群拥有一个或多个namenode。每个namenode管理自己的命名空间。一个namenode仅属于一个集群。同一集群内的namenode共享该集群的物理存储。不同集群间的命名空间保持相互独立。

运维人员根据存储需求决定集群中每个namenode上存储的内容。例如,他们可能将所有用户数据(/user/)放在一个namenode中,将所有feed数据(/data)放在另一个namenode中,将所有项目(/projects)放在另一个namenode中,等等。

使用ViewFs实现每个集群的全局命名空间

为了与旧世界保持透明性,ViewFs文件系统(即客户端挂载表)被用于为每个集群创建独立的集群命名空间视图,这类似于旧世界中的命名空间。客户端挂载表类似于Unix挂载表,它们使用旧的命名约定来挂载新的命名空间卷。下图展示了一个挂载表挂载四个命名空间卷/user/data/projects/tmp

Typical Mount Table for each Cluster

ViewFs实现了Hadoop文件系统接口,类似于HDFS和本地文件系统。从某种意义上说,它是一个简单的文件系统,因为它只允许链接到其他文件系统。由于ViewFs实现了Hadoop文件系统接口,它可以透明地与Hadoop工具协同工作。例如,所有shell命令都可以像操作HDFS和本地文件系统一样操作ViewFs。

在每个集群的配置中,默认文件系统被设置为该集群的挂载表,如下所示(请与单Namenode集群中的配置进行比较)。

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

URI中viewfs://协议后的权限部分表示挂载表名称。建议集群的挂载表应以集群名称命名。随后Hadoop系统会在配置文件中查找名为"clusterX"的挂载表。运维人员需确保所有网关和服务机器都包含所有集群的挂载表配置,对于每个集群,其默认文件系统需按上述方式设置为该集群对应的ViewFs挂载表。

挂载表的挂载点是在标准的Hadoop配置文件中指定的。所有viewfs的挂载表配置项都以fs.viewfs.mounttable.为前缀。使用link标签来指定链接到其他文件系统的挂载点。建议将挂载点名称设置为与链接的文件系统目标位置相同。对于挂载表中未配置的所有命名空间,可以通过linkFallback将它们回退到默认文件系统。

在下面的挂载表配置中,命名空间/data被链接到文件系统hdfs://nn1-clusterx.example.com:8020/data/project被链接到文件系统hdfs://nn2-clusterx.example.com:8020/project。所有未在挂载表中配置的命名空间,例如/logs,将被链接到文件系统hdfs://nn5-clusterx.example.com:8020/home

<configuration>
  <property>
    <name>fs.viewfs.mounttable.clusterX.link./data</name>
    <value>hdfs://nn1-clusterx.example.com:8020/data</value>
  </property>
  <property>
    <name>fs.viewfs.mounttable.clusterX.link./project</name>
    <value>hdfs://nn2-clusterx.example.com:8020/project</value>
  </property>
  <property>
    <name>fs.viewfs.mounttable.clusterX.link./user</name>
    <value>hdfs://nn3-clusterx.example.com:8020/user</value>
  </property>
  <property>
    <name>fs.viewfs.mounttable.clusterX.link./tmp</name>
    <value>hdfs://nn4-clusterx.example.com:8020/tmp</value>
  </property>
  <property>
    <name>fs.viewfs.mounttable.clusterX.linkFallback</name>
    <value>hdfs://nn5-clusterx.example.com:8020/home</value>
  </property>
</configuration>

或者,我们可以通过linkMergeSlash将挂载表的根目录与另一个文件系统的根目录合并。在下面的挂载表配置中,clusterY的根目录与位于hdfs://nn1-clustery.example.com:8020的根文件系统进行了合并。

<configuration>
  <property>
    <name>fs.viewfs.mounttable.clusterY.linkMergeSlash</name>
    <value>hdfs://nn1-clustery.example.com:8020/</value>
  </property>
</configuration>

路径名使用模式

因此在集群X上,core-site.xml被设置为使用该集群挂载表作为默认文件系统时,典型的路径名是

  1. /foo/bar

    • 这等同于viewfs://clusterX/foo/bar。如果在旧的未联邦化环境中使用此类路径名,那么向联邦环境的过渡将是透明的。
  2. viewfs://clusterX/foo/bar

    • 虽然这是一个有效的路径名,但最好使用/foo/bar,因为它允许在需要时将应用程序及其数据透明地迁移到另一个集群。
  3. viewfs://clusterY/foo/bar

    • 这是一个用于引用其他集群(如集群Y)上路径名的URI。具体来说,将文件从集群Y复制到集群Z的命令如下:
      distcp viewfs://clusterY/pathSrc viewfs://clusterZ/pathDest
      
  4. viewfs://clusterX-webhdfs/foo/bar

    • 这是一个通过WebHDFS文件系统访问文件的URI。
  5. http://namenodeClusterX:http_port/webhdfs/v1/foo/barhttp://proxyClusterX:http_port/foo/bar

    • 这些是通过WebHDFS REST API和HDFS代理访问文件的HTTP URL。请注意,它们与之前相同。

路径名使用最佳实践

在集群内部时,建议使用上述类型(1)的路径名,而非类似(2)的完整限定URI。此外,应用程序不应依赖挂载点信息,也不应使用类似hdfs://namenodeContainingUserDirs:port/joe/foo/bar的路径来引用特定名称节点中的文件,而应使用/user/joe/foo/bar路径。

跨命名空间重命名路径

请注意,在旧版本中无法跨名称节点或集群重命名文件或目录。在新版本中同样如此,但增加了一个限制。例如,在旧版本中可以执行以下命令。

rename /user/joe/myStuff /data/foo/bar

如果/user/data实际上存储在集群内的不同名称节点上,这在新环境中将无法工作。

使用Nfly挂载点实现多文件系统I/O

HDFS和其他分布式文件系统通过某种冗余机制(如块复制或更复杂的分布式编码)提供数据弹性。然而,现代架构可能由多个Hadoop集群、企业级存储设备组成,部署在本地和云端。Nfly挂载点使得单个逻辑文件能够被多个文件系统同步复制。该设计适用于相对较小的文件(最大1GB)。由于相关逻辑运行在使用ViewFs(如FsShell或MapReduce任务)的单个客户端JVM中,其性能通常受限于单核CPU/单网络链路。

基础配置

通过以下示例来理解Nfly的基本配置。假设我们希望保持目录ads在由URI表示的三个文件系统上复制:uri1uri2uri3

  <property>
    <name>fs.viewfs.mounttable.global.linkNfly../ads</name>
    <value>uri1,uri2,uri3</value>
  </property>

注意属性名中连续的两个..。它们之所以出现是因为挂载点高级调优的空设置,我们将在后续章节展示。属性值是以逗号分隔的URI列表。

URI可能指向不同区域的不同集群 hdfs://datacenter-east/ads, s3a://models-us-west/ads, hdfs://datacenter-west/ads 或者在最简单的情况下指向同一文件系统下的不同目录,例如 file:/tmp/ads1, file:/tmp/ads2, file:/tmp/ads3

在全局路径viewfs://global/ads下执行的所有修改操作,如果底层系统可用,将会传播到所有目标URI。

例如,如果我们通过hadoop shell创建一个文件

hadoop fs -touchz viewfs://global/ads/z1

我们将在后续配置中通过本地文件系统找到它

ls -al /tmp/ads*/z1
-rw-r--r--  1 user  wheel  0 Mar 11 12:17 /tmp/ads1/z1
-rw-r--r--  1 user  wheel  0 Mar 11 12:17 /tmp/ads2/z1
-rw-r--r--  1 user  wheel  0 Mar 11 12:17 /tmp/ads3/z1

从全局路径读取数据时,将由第一个不引发异常的文件系统处理。文件系统的访问顺序取决于它们当前是否可用以及是否存在拓扑顺序。

高级配置

挂载点 linkNfly 可以通过传递以逗号分隔的键值对参数列表进行进一步配置。以下是当前支持的参数。

minReplication=int 用于确定在不引发异常的情况下必须处理写入操作的最小目标数量,如果低于该值则nfly写入将失败。若minReplication值高于目标URI数量则属于配置错误。默认值为2。

如果minReplication低于目标URI的数量,我们可能会有一些目标URI没有最新的写入。可以通过采用以下设置控制的更昂贵的读取操作来补偿

readMostRecent=boolean 如果设置为true,将导致Nfly客户端检查所有目标URI下的路径,而不仅仅是基于拓扑顺序的第一个路径。在目前所有可用的路径中,将处理修改时间最新的那个。

repairOnRead=boolean 如果设置为true,将导致Nfly将最新的副本复制到过时的目标,这样后续读取可以再次从最近的副本廉价地完成。

网络拓扑

Nfly 旨在从"最近"的目标 URI 满足读取请求。

为此,Nfly将Rack Awareness的概念扩展到了目标URI的权限范围。

Nfly应用NetworkTopology来解析URI的权限。在异构设置中最常用的是基于脚本的映射。我们可以使用一个提供以下拓扑映射的脚本

URI 拓扑结构
hdfs://datacenter-east/ads /us-east/onpremise-hdfs
s3a://models-us-west/ads /us-west/aws
hdfs://datacenter-west/ads /us-west/onpremise-hdfs

如果目标URI没有权限部分,例如file:/,Nfly会注入客户端的本地节点名称。

Nfly配置示例

  <property>
    <name>fs.viewfs.mounttable.global.linkNfly.minReplication=3,readMostRecent=true,repairOnRead=false./ads</name>
    <value>hdfs://datacenter-east/ads,hdfs://datacenter-west/ads,s3a://models-us-west/ads,file:/tmp/ads</value>
  </property>

Nfly文件创建的工作原理

FileSystem fs = FileSystem.get("viewfs://global/", ...);
FSDataOutputStream out = fs.create("viewfs://global/ads/f1");
out.write(...);
out.close();

上述代码将导致以下执行结果。

  1. 在每个目标URI下创建一个不可见文件_nfly_tmp_f1,例如hdfs://datacenter-east/ads/_nfly_tmp_f1hdfs://datacenter-west/ads/_nfly_tmp_f1等。这是通过调用底层文件系统的create方法实现的,并返回一个包装了所有四个输出流的FSDataOutputStream对象out

  2. 因此,对out的每次后续写入都可以转发到每个包装的流。

  3. out.close时,所有流都会被关闭,文件会从_nfly_tmp_f1重命名为f1。所有文件都会获得相同的修改时间,该时间对应客户端系统在此步骤开始的时间。

  4. 如果至少有minReplication个目标节点成功完成步骤1-3且没有失败,Nfly会认为该事务在逻辑上已提交;否则它会尽力尝试清理临时文件。

请注意,由于步骤4是基于最大努力原则执行的,客户端JVM可能会崩溃且无法恢复工作,因此建议配置某种定时任务来清理这些_nfly_tmp文件。

常见问题

  1. 当我从非联邦环境迁移到联邦环境时,需要跟踪不同卷的namenode,该如何操作?

    不需要。参考上面的示例——您要么使用相对路径并利用默认文件系统,要么将路径从hdfs://namenodeCLusterX/foo/bar改为viewfs://clusterX/foo/bar

  2. 如果运维人员将一些文件从集群中的一个namenode移动到另一个namenode,会发生什么情况?

    操作可能会将文件从一个namenode移动到另一个namenode,以解决存储容量问题。它们会以避免应用程序中断的方式进行此操作。让我们来看一些例子。

    • 示例1:/user/data最初位于同一个namenode上,后来由于容量问题需要分别放在不同的namenode上。实际上,运维人员会为/user/data创建独立的挂载点。在变更前,/user/data的挂载点都指向同一个namenode,比如namenodeContainingUserAndData。运维人员将更新挂载表,使挂载点分别改为namenodeContaingUsernamenodeContainingData

    • 示例2:所有项目最初都部署在一个namenode上,但后来需要两个或更多namenode。ViewFs允许挂载类似/project/foo/project/bar的路径。这使得可以更新挂载表以指向相应的namenode。

  3. 每个挂载表是在 core-site.xml 中还是单独存放在自己的文件中?

    计划是将挂载表保存在单独的文件中,并通过xincluding方式包含到core-site.xml中。虽然可以在每台机器上本地保存这些文件,但最好使用HTTP从中央位置访问它们。

  4. 配置中应该只包含一个集群的挂载表定义,还是所有集群的挂载表定义?

    配置应包含所有集群的挂载定义,因为需要能够访问其他集群中的数据,例如使用distcp时。

  5. 考虑到操作可能会随时间改变挂载表,挂载表实际在何时被读取?

    挂载表在作业提交到集群时被读取。core-site.xml中的XInclude会在作业提交时展开。这意味着如果挂载表发生变更,则需要重新提交作业。基于这个原因,我们希望实现merge-mount功能,这将大幅减少修改挂载表的需求。此外,我们计划未来通过另一种在作业启动时初始化的机制来读取挂载表。

  6. JobTracker(或Yarn的Resource Manager)本身会使用ViewFs吗?

    不会,它不需要。NodeManager也不需要。

  7. ViewFs是否只允许在顶层挂载?

    不是;它更为通用。例如,可以挂载/user/joe/user/jane。在这种情况下,挂载表中会为/user创建一个内部只读目录。对/user的所有操作都是有效的,只是/user是只读的。

  8. 一个应用程序需要跨集群工作并持久化存储文件路径。它应该存储哪些路径?

    你应该存储viewfs://cluster/path类型的路径名称,与运行应用程序时使用的路径相同。只要操作以透明方式移动数据,这可以使你免受集群内namenode之间数据移动的影响。但如果数据从一个集群移动到另一个集群,这并不能提供保护;无论如何,在旧版(联邦前)环境中也无法保护你免受这种跨集群数据移动的影响。

  9. 关于委托令牌(Delegation tokens)的处理方式?

    系统会自动处理以下委托令牌:您提交作业的目标集群(包括该集群挂载表中的所有挂载卷),以及MapReduce作业的输入输出路径(包括通过挂载表为指定输入输出路径挂载的所有卷)。此外,还提供了一种方法可以在特殊情况下向基础集群配置添加额外的委托令牌。

不想更改方案或难以将挂载表配置复制到所有客户端?

请参考查看文件系统过载方案指南

基于正则表达式模式的挂载点

The view file system mount points were a Key-Value based mapping system. It is not friendly for user cases which mapping config could be abstracted to rules. E.g. Users want to provide a GCS bucket per user and there might be thousands of users in total. The old key-value based approach won’t work well for several reasons:

  1. 挂载表由文件系统客户端使用。将配置分发到所有客户端存在成本,应尽可能避免。View File System Overload Scheme Guide可通过集中式挂载表管理来帮助分发。但每次变更仍需更新挂载表。如果提供基于规则的挂载表,则可大幅减少变更需求。

  2. 客户端必须理解挂载表中的所有键值对。当挂载项增长到数千个条目时,这种设计并不理想。例如即使用户只需要访问一个文件系统,也可能初始化数千个文件系统。而且配置本身在大规模使用时将变得臃肿。

理解差异

在基于键值的挂载表中,视图文件系统将每个挂载点视为一个分区。存在多个文件系统API会导致对所有分区的操作。例如,有一个包含多个挂载点的HDFS集群。用户希望运行"hadoop fs -put file viewfs://hdfs.namenode.apache.org/tmp/"命令将数据从本地磁盘复制到我们的HDFS集群。该命令将触发ViewFileSystem调用setVerifyChecksum()方法,该方法会为每个挂载点初始化文件系统。对于基于正则表达式规则的挂载表条目,在解析之前我们无法知道对应的路径是什么。因此在这种情况下会忽略基于正则表达式的挂载表条目。文件系统(ChRootedFileSystem)将在访问时创建。但底层文件系统会被ViewFileSystem的内部缓存所缓存。

<property>
    <name>fs.viewfs.rename.strategy</name>
    <value>SAME_FILESYSTEM_ACROSS_MOUNTPOINT</value>
</property>

基础正则表达式链接映射配置

这是一个基础正则表达式挂载点配置的示例。${username} 是 Java 正则表达式中的命名捕获组。

<property>
    <name>fs.viewfs.mounttable.hadoop-nn.linkRegx./^(?<username>\\w+)</name>
    <value>gs://${username}.hadoop.apache.org/</value>
</property>

解析示例。

viewfs://hadoop-nn/user1/dir1 => gs://user1.hadoop.apache.org/dir1
viewfs://hadoop-nn/user2 => gs://user2.hadoop.apache.org/

src/key的格式为

fs.viewfs.mounttable.${VIEWNAME}.linkRegx.${REGEX_STR}

使用拦截器实现正则表达式链接映射

Interceptor(拦截器)是一种在解析过程中用于修改源路径或目标路径的机制。它是可选的,可用于满足诸如替换特定字符或某些单词等用户场景。拦截器仅对正则表达式挂载点生效。目前RegexMountPointResolvedDstPathReplaceInterceptor是唯一内置的拦截器。

这是一个设置了RegexMountPointResolvedDstPathReplaceInterceptor的正则表达式挂载点条目示例。

<property>
    <name>fs.viewfs.mounttable.hadoop-nn.linkRegx.replaceresolveddstpath:_:-#./^(?<username>\\w+)</name>
    <value>gs://${username}.hadoop.apache.org/</value>
</property>

replaceresolveddstpath:_:-是一个拦截器设置。"replaceresolveddstpath"表示拦截器类型,"_"表示待替换的字符串,"-"表示替换后的字符串。

解析示例。

viewfs://hadoop-nn/user_ad/dir1 => gs://user-ad.hadoop.apache.org/dir1
viewfs://hadoop-nn/user_ad_click => gs://user-ad-click.hadoop.apache.org/

src/key的格式为

fs.viewfs.mounttable.${VIEWNAME}.linkRegx.${REGEX_STR}
fs.viewfs.mounttable.${VIEWNAME}.linkRegx.${interceptorSettings}#.${srcRegex}

附录:挂载表配置示例

通常,用户无需定义挂载表或修改core-site.xml即可使用挂载表。这些操作由运维团队完成,正确的配置会像当前处理core-site.xml一样设置在适当的网关机器上。

挂载表可以在core-site.xml中定义,但最好在core-site.xml中使用间接引用来引用单独的配置文件,例如mountTable.xml。将以下配置元素添加到core-site.xml中以引用mountTable.xml

<configuration xmlns:xi="http://www.w3.org/2001/XInclude"> 
  <xi:include href="mountTable.xml" />
</configuration> 

在文件mountTable.xml中,定义了一个名为"clusterX"的挂载表,用于假设由三个名称节点管理的三个命名空间卷组成的联邦集群

  1. nn1-clusterx.example.com:8020,
  2. nn2-clusterx.example.com:8020,以及
  3. nn3-clusterx.example.com:8020.

这里 /home/tmp 位于由namenode nn1-clusterx.example.com:8020管理的命名空间中,而项目 /foo/bar 则托管在联邦集群的其他namenode上。主目录基础路径设置为 /home,以便每个用户都可以使用FileSystem/FileContext中定义的getHomeDirectory()方法访问其主目录。

<configuration>
  <property>
    <name>fs.viewfs.mounttable.clusterX.homedir</name>
    <value>/home</value>
  </property>
  <property>
    <name>fs.viewfs.mounttable.clusterX.link./home</name>
    <value>hdfs://nn1-clusterx.example.com:8020/home</value>
  </property>
  <property>
    <name>fs.viewfs.mounttable.clusterX.link./tmp</name>
    <value>hdfs://nn1-clusterx.example.com:8020/tmp</value>
  </property>
  <property>
    <name>fs.viewfs.mounttable.clusterX.link./projects/foo</name>
    <value>hdfs://nn2-clusterx.example.com:8020/projects/foo</value>
  </property>
  <property>
    <name>fs.viewfs.mounttable.clusterX.link./projects/bar</name>
    <value>hdfs://nn3-clusterx.example.com:8020/projects/bar</value>
  </property>
</configuration>