从 HDFS Observer NameNode 进行一致性读取

目的

本指南概述了 HDFS Observer NameNode 功能,以及如何在典型的启用 HA 的集群中配置/安装该功能。如需详细的技术设计概述,请查看附加到 HDFS-12943 的文档。

背景

在启用了 HA 的 HDFS 集群中(有关更多信息,请查看 HDFSHighAvailabilityWithQJM),有一个活动 NameNode 和一个或多个备用 NameNode。活动 NameNode 负责处理所有客户端请求,而备用 NameNode 只需通过跟踪来自 JournalNode 的编辑日志以及通过接收来自所有 DataNode 的块报告来维护有关命名空间的最新信息,以及块位置信息。此架构的一个缺点是活动 NameNode 可能成为单一瓶颈,并且会被客户端请求过载,尤其是在繁忙的集群中。

HDFS Observer NameNode 特性中的“一致性读取”通过引入一种称为Observer NameNode的新类型 NameNode 来解决上述问题。与备用 NameNode 类似,Observer NameNode 会保持自身关于命名空间和块位置信息的最新状态。此外,它还具有提供一致性读取的能力,就像活动 NameNode 一样。由于在典型环境中,读取请求占大多数,因此这有助于平衡 NameNode 流量并提高整体吞吐量。

架构

在新架构中,HA 集群可以由处于 3 种不同状态的 namenode 组成:活动、备用和观察者。状态转换可以在活动和备用、备用和观察者之间发生,但不能直接在活动和观察者之间发生。

为了确保单个客户端内的写后读一致性,在 RPC 头中引入了一个状态 ID,该 ID 使用 NameNode 内的事务 ID 实现。当客户端通过活动 NameNode 执行写入时,它使用 NameNode 中的最新事务 ID 更新其状态 ID。在执行后续读取时,客户端将此状态 ID 传递给 Observer NameNode,后者将针对其自身的事务 ID 进行检查,并在提供读取请求之前确保其自身的事务 ID 已赶上请求的状态 ID。这确保了单个客户端的“读取自己的写入”语义。在“维护客户端一致性”部分中讨论了在带外通信的情况下维护多个客户端之间的一致性。

编辑日志尾随对于 Observer NameNode 至关重要,因为它直接影响事务在活动 NameNode 中应用的时间和在 Observer NameNode 中应用的时间之间的延迟。引入了一种名为“编辑尾随快速路径”的新编辑日志尾随机制,以显著减少此延迟。这是建立在现有的正在进行的编辑日志尾随特性的基础之上的,并进一步进行了改进,例如基于 RPC 的尾随而不是 HTTP、JournalNode 上的内存缓存等。有关更多详细信息,请参阅附加到 HDFS-13150 的设计文档。

还引入了新的客户端代理提供程序。ObserverReadProxyProvider 继承了现有的 ConfiguredFailoverProxyProvider,应使用它来替换后者,以启用从 Observer NameNode 读取。在提交客户端读取请求时,代理提供程序将首先尝试群集中可用的每个 Observer NameNode,并且仅在所有前者都失败时才回退到 Active NameNode。类似地,ObserverReadProxyProviderWithIPFailover 被引入以在 IP 故障转移设置中替换 IPFailoverProxyProvider。

维护客户端一致性

如上所述,客户端“foo”将在每次向 Active NameNode 发出请求时更新其状态 ID,其中包括所有写入操作。定向到 Observer NameNode 的任何请求都将等待 Observer 看到此事务 ID,确保客户端能够读取其自己的所有写入。但是,如果“foo”向客户端“bar”发送带外(即非 HDFS)消息,告诉它已执行写入,则“bar”的后续读取可能看不到“foo”最近的写入。为了防止这种不一致的行为,已添加了一个新的 msync() 或“元数据同步”命令。当对客户端调用 msync() 时,它将针对 Active NameNode 更新其状态 ID - 一个非常轻量级的操作 - 以便后续读取保证与 msync() 的点一致。因此,只要“bar”在执行读取之前调用 msync(),就保证可以看到“foo”所做的写入。

为了使用 msync(),应用程序不必一定要进行任何代码更改。在启动时,客户端将在针对 Observer 执行任何读取之前自动调用 msync(),以便在客户端初始化之前执行的任何写入都将可见。此外,ObserverReadProxyProvider 支持一种可配置的“自动 msync”模式,它将在一些可配置的时间间隔自动执行 msync(),以防止客户端看到比时间界限更旧的数据。这会带来一些开销,因为每次刷新都需要向 Active NameNode 发出 RPC,因此默认情况下禁用它。

部署

配置

要启用从 Observer NameNode 进行一致的读取,您需要向 hdfs-site.xml 添加一些配置

  • dfs.namenode.state.context.enabled - 启用 NameNode 维护和更新服务器状态和 ID。

这将导致 NameNode 创建对齐上下文实例,它跟踪当前服务器状态 ID。服务器状态 ID 将被带回客户端。默认情况下禁用它以优化 Observer 读取用例的性能。但这必须开启才能使用 Observer NameNode 功能。

    <property>
       <name>dfs.namenode.state.context.enabled</name>
       <value>true</value>
    </property>
  • dfs.ha.tail-edits.in-progress - 启用对正在进行的编辑日志进行快速尾随。

这通过正在进行的编辑日志以及其他机制(例如基于 RPC 的编辑日志获取、JournalNodes 中的内存缓存等)实现了快速的编辑日志尾随。它在默认情况下处于禁用状态,但必须为观察者名称节点功能将其打开

    <property>
      <name>dfs.ha.tail-edits.in-progress</name>
      <value>true</value>
    </property>
  • dfs.ha.tail-edits.period - 备用/观察者名称节点应从 JournalNodes 获取编辑的频率。

这决定了观察者名称节点相对于活动名称节点的陈旧程度。如果太大,RPC 时间将增加,因为客户端请求将在 RPC 队列中等待更长时间,然后观察者尾随编辑日志并赶上活动名称节点的最新状态。默认值为 1 分钟。强烈建议将其配置为一个低得多的值。还建议在使用较低值时启用回退;请参见下文。

    <property>
      <name>dfs.ha.tail-edits.period</name>
      <value>0ms</value>
    </property>
  • dfs.ha.tail-edits.period.backoff-max - 备用/观察者名称节点在尾随编辑时是否应执行回退。

这决定了备用/观察者在尝试从 JournalNodes 尾随编辑并发现没有可用编辑时的行为。当编辑尾随周期非常低但集群负载不高时,这是常见的情况。如果没有此配置,这种情况将导致备用/观察者利用率很高,因为它不断尝试读取编辑,即使没有可用编辑。启用此配置后,当编辑尾随尝试返回 0 个编辑时,将执行指数回退。此配置指定编辑尾随尝试之间的最大等待时间。

    <property>
      <name>dfs.ha.tail-edits.period.backoff-max</name>
      <value>10s</value>
    </property>
  • dfs.journalnode.edit-cache-size.bytes - JournalNodes 上的内存缓存大小(以字节为单位)。

这是 JournalNode 端用于存储编辑的内存缓存的大小(以字节为单位)。该缓存用于通过基于 RPC 的尾随提供编辑。仅当 dfs.ha.tail-edits.in-progress 打开时,这才是有效的。

    <property>
      <name>dfs.journalnode.edit-cache-size.bytes</name>
      <value>1048576</value>
    </property>
  • dfs.namenode.accesstime.precision – 是否为 HDFS 文件启用访问时间。

强烈建议禁用此配置。如果启用,这会将 getBlockLocations 调用变为写入调用,因为它需要持有写入锁以更新打开的文件的时间。因此,请求将在所有观察者名称节点上失败,并最终回退到活动名称节点。结果,RPC 性能将下降。

    <property>
      <name>dfs.namenode.accesstime.precision</name>
      <value>0</value>
    </property>

新的管理命令

引入了一个新的 HA 管理命令,用于将备用名称节点转换为观察者状态

haadmin -transitionToObserver

请注意,这只能在备用名称节点上执行。在活动名称节点上调用此命令时,将引发异常。

类似地,现有的 transitionToStandby 也可以在观察者名称节点上运行,将其转换为备用状态。

注意:观察者 NameNode 参与故障转移的功能尚未实现。因此,如下一节所述,您应该仅使用 transitionToObserver 来启动观察者。ZKFC 可以在观察者 NameNode 上启用,但当 NameNode 处于观察者状态时,它不会执行任何操作。在 NameNode 过渡到备用状态后,ZKFC 将参与活动选举。

部署详细信息

要启用观察者支持,首先您需要一个具有 2 个以上名称节点的高可用性 HDFS 集群。然后,您需要将备用 NameNode 过渡到观察者状态。最低设置是在集群中运行 3 个名称节点,一个活动节点、一个备用节点和一个观察者节点。对于大型 HDFS 集群,我们建议根据读取请求的强度和高可用性要求运行两个或更多个观察者。

请注意,当启用自动故障转移时,观察者 NameNode 目前无法完全集成。如果启用了 dfs.ha.automatic-failover.enabled,在观察者 NameNode 上运行 ZKFC 的唯一好处是,在您将 NameNode 过渡到备用后,它将自动加入活动选举。如果您不希望这样做,则可以在观察者 NameNode 上禁用 ZKFC。除此之外,您还需要向 transitionToObserver 命令添加 forcemanual 标志

haadmin -transitionToObserver -forcemanual

将来,此限制将解除。

客户端配置

希望使用观察者 NameNode 进行读取访问的客户端可以在客户端的 hdfs-site.xml 配置文件中为代理提供程序实现指定 ObserverReadProxyProvider 类

<property>
    <name>dfs.client.failover.proxy.provider.<nameservice></name>
    <value>org.apache.hadoop.hdfs.server.namenode.ha.ObserverReadProxyProvider</value>
</property>

不希望使用观察者 NameNode 的客户端仍然可以使用现有的 ConfiguredFailoverProxyProvider,并且不应该看到任何行为变化。

希望利用“自动 msync”功能的客户端应调整以下配置。这将指定一段时间,在此期间,如果客户端的状态 ID 尚未从活动 NameNode 更新,则将自动执行 msync()。如果将其指定为 0,则将在每次读取操作之前执行 msync()。如果这是一个正时间段,则将在每次请求读取操作并且在该时间段内未联系活动节点时执行 msync()。如果这是负数(默认值),则不会执行自动 msync()

<property>
    <name>dfs.client.failover.observer.auto-msync-period.<nameservice></name>
    <value>500ms</value>
</property>