美文网首页Hadoop运维日常hadoop
Observer NameNode 的读写一致性保证

Observer NameNode 的读写一致性保证

作者: xudong1991 | 来源:发表于2021-09-04 09:12 被阅读0次

背景

Hadoop HDFS 3.x 引入了 Observer NameNode 这个新角色之后,典型的集群部署从一主一备,变为一主一备一OBNN,其中:

  1. Active NameNode 处理所有写类型请求
  2. Standby NameNode 做 HA 使用。
  3. Observer NameNode 处理所有的读类型请求。

实际上这就是读写分离,在集群读负载特别重时,甚至可以配置多个 Observer NameNode,从而更加分散读负载,更好地提升集群的处理能力。

几个典型读写分离场景如下:

  1. 写入文件 -> 读取文件
  2. append 文件 -> 读取文件
  3. delete 文件 -> 读取文件
  4. delete 目录 -> list 目录
  5. delete 目录 -> count 目录
  6. 其它

在做了读写分离之后,如果控制不当,就有可能导致“读”类型操作最终得到不符合预期的结果,包括但不限于:

  1. 读取到已被删除的文件。
  2. 读取到不完整的文件内容。
  3. list 得到不完整的目录内容。
  4. count 得到错误的结果。
  5. 其它

Observer NameNode 的主要难点就是如何保证读写的一致性,这也是我们最关心的点。

读写一致性保证

在上面列出的几个场景中,“写文件 -> 读文件” 是最常见的情况,因此,以它为例来进行分析。其它几种场景的原理也类似。

对于 "写文件 -> 读文件“ 的场景,有下面几种可能的情况,分别使用不同的机制来保证一致性。

本客户端写文件完成后,自己读取该文件。

自写自读,这是最简单的情况,也是 Observer NN 要解决的最基础的问题。为了解决该问题,HDFS 引入了一个新的的 RPC header 成员:state id,几个要点如下:

  1. state id 的取值实际上就是已有的 NameSystem Transaction ID(即 EditLog ID),这是一个单调递增的 long 型整数,由 Active NN 负责维护,Active NN 每执行一个写操作后便递增此 id。
  2. client 从 Active NN 获取最新的 state id,并保存在自己的内存中。
  3. client 每同 Active NN 执行一次 RPC 请求后,就会更新一次自己的 state id(通过 RPC response 从 Active NN 带回),以便尽可能地让自己跟踪到 NN 的最新状态。
  4. client 每次向 Observer NN 发送 RPC 请求时,都会带上这个 state id.
  5. Observer NN 收到 RPC 请求后:
    1. 如果 Observer NN 发现自己目前的 transaction id 比客户端的 state id 更大,那就说明他早就 apply 了这个客户端之前的写操作,那么他就可以立即执行,并给客户端返回结果。
    2. 如果 Observer NN 发现自己目前的 transaction id 没有客户端的 state id 大,那么说明他还没有来得及 apply 这个客户端之前的写操作,那么它暂时还不能处理这个 RPC,需要一直等到它的 transaction id 大于等于客户端的 state id 之后,才能开始处理。

本客户端写文件完成后,其它客户端启动进程,读取该文件。

自写之后他读,这也是比较简单的情况,为解决该问题,Observer NN 引入了一个新的操作,即:每当客户端启动的时候,都会主动向 Active NN 调用一次 msync(),确保自己的 state id 已更新到最新状态,在此基础上,它去读取之前已经写入完成的文件就没有问题。

关于 msync(),这是 Observer NN 新引入的一个 RPC 调用,也是一个非常轻量级的 PRC,所做的事情也很简单,即:从 Active NN 拿到最新的 transaction id,然后将自己的 static id 更新到最新状态。

两个客户端进程都早已启动,本客户端写文件完成后,其它客户端立即去读。

自写同时他读,这是最棘手的情况,处理不好很可能导致其它客户端读取到不符合预期的结果。

  1. 理想情况
    1. Observer NN 已经完整的 apply 了这个文件的 create + close 操作,所以已经 close 了这个文件,此时,client 可以读取到完整的文件。
  2. 几个可能的出错情况
    1. Observer NN 还没有来得及 apply 这个文件的 create 操作,因此无法找到这个文件,报错。
    2. Observer NN 已经 apply 这个文件的 create 操作,但尚未 apply 文件的 close 操作,那么此时在 Observer NN 这里,这个文件处于打开状态, 此时 client 可能会读到不完整的文件内容。

对于这种最复杂的场景,其实并没有什么好办法,在这种情况下,其它客户端只能等待一段时间之后,才能确保从 OBNN 读到最新的内容,而这个间隔的长短就成为关键,我们所能做的,就是尽量想办法,压缩这个间隔。目前的话,对于客户端而言,就是被动和主动两个策略。

两种等待策略

  1. 客户端被动等待
    即客户端什么都不做,就等 OBNN 自己追赶 ANN,这是最简单的策略,以后应该也会成为我们主要的方案,在这种客户端完全无为的情况下,他所要等待的间隔,其实就是下面这个流程的耗时:

    ANN 执行写操作完成并将 EditLog 写到 JN 集群 -> OBNN 从 JN 集群拉取到 EditLog -> OBNN 将 EditLog 应用到自身

    目前的话,在软件层面,通过各种措施和优化,这个间隔几乎已经被压缩到了极限。因此,这个间隔的最终值,基本取决于 NN 机器性能、JN 机器性能、网络性能等等硬件指标。

    从现网机器的测试结果来看,这个间隔大概在 1s-5s 左右(现网实测结果:正常情况下 10ms,最差情况下 3s)。

  2. 客户端主动更新
    除了被动依靠 OBNN 自己追赶 ANN 以外,Client 也可以发挥主动性,保证自己尽量拿到最新的结果,即:

    1. 客户端首先主动对 ANN 来一次 msync() 调用,这是一个新增的 RPC 调用,执行非常简单,就是拿到 ANN 目前最新的 state id。
    2. 客户端接下来向 OBNN 发送 RPC 请求时,会带上这个 state id。而 OBNN 处理 RPC 请求时,如果它发现自身目前的状态比较老,还不足以处理这个 RPC(即:自身的 state id 小于客户端要求的 state id),那 OBNN 会暂时推迟这个 RPC 的处理,直到自己的状态足够新(即:自身的 state id 已经大于等于客户端要求的 state id 之后),才会开始处理这个 RPC,并给客户端返回结果。

    主动策略需要客户端周期性的从 ANN 拿到最新的 state id,为此,HDFS 3.x 为客户端添加了一个自动的周期性 msync() 机制, mysnc() 周期通过 可配,默认为 -1,即禁用自动 msync 机制。

    假设这个周期配置为 10s,则客户端每隔10s,就从 ANN 获取一次最新的 state id,这就能保证:所有 10s 以前的 ANN 写操作,client 都可以从 OBNN 处读取到正确结果(但注意它发给 OBNN 的 RPC 请求,可能需要在 OBNN 处停留一会儿,以等待 OBNN 满足处理条件).

    目前现网最繁忙的 HDFS 集群,其客户端大概有 5W 个左右,如果自动 msync 周期配置为 10s,那么平均每秒钟会产生 5000 个 msync RPC 请求,量比较大。但是 msync() 调用比较特殊,它其实并没有真正做什么(既不会加写锁,也不会加读锁,也不会对 NameSystem 做任何操作),只是简单在 RPC client 和 RPC server 之间同步了一下 state id,因此,这个 RPC 对 ANN 造成的性能影响很小,可以忽略。

    因此,作为一个兜底的机制,建议开启客户端自动 msync() 机制,以保证一个最长的等待时间,这个时间建议配置为 10s。

一致性结论

三种可能的情况:

  1. 客户端自写自读
    这是最简单的情况,此时可以百分百确保读写一致。

  2. 客户端 A 写入完成之后,客户端 B 启动进程并读取
    这是现网最常见的情况,此时,也可以百分百确保读写一致。

  3. 客户端 A、B 均为常驻进程,A 写入完成之后,B 立刻开始读取
    这是最复杂的情况,在这种情况下,客户端 B 必须要等待一段时间,才能确保从 OBNN 读到准确的结果,而这个间隔的长短就成为关键:

    1. 一般情况下,按照现网的机器配置,客户端需要等待 1s-5s 左右(现网实测结果:正常情况下 10ms,最差情况下 3s),就能保证 OBNN 追上 ANN,从而能从 OBNN 读取到正确的结果。
    2. 最差情况下,客户端需要一个兜底的机制,即客户端自动 msync() 机制,自动 msync() 周期建议配置为 10s,这样可以保证客户端最长需要等待 10s,然后即可从 OBNN 处读取到正确结果。当然,如果客户端等不了这么久,也可以将这个周期配置的更短,但同时也将加重 ANN 的负载(msync 请求只能由 ANN 处理)。

Observer NameNode 的几个关键前置特性

Observer NameNode 有几个比较重要的前置特性,基本上都是针对 EditLog tail 机制的优化,这也直接决定了 OBNN/SBNN 和 ANN 的差距到底会有多大,如果需要将 OBNN 特性合入低版本 Hadoop,那么这几个前置特性是首先要合入的。

  1. RPC tail editlog
    默认情况下,SBNN/OBNN 通过 http 方式从 JN 集群拉取 EditLog,现在也可以通过 RPC 方式拉取,这会比较明显得提高拉取性能。

  2. Fast tail editlog
    默认情况下,SBNN/OBNN 只会从 JN 集群拉取 Finalized 状态的 EditLog segment 文件(即写完并 close 的 EditLog segment 文件),按照现网配置,一个 EditLog segment 文件从 open 到 close 固定为2分钟,因此 SBNN/OBNN 与 ANN 至少会有2分钟的差距,这个太长。

    增加了快速 tail 机制后,SBNN/OBNN 不用等到一个 EditLog segment 文件写完,而是可以尽快拉取目前处于 inprogress 状态的 EditLog segment 文件,从而尽量缩小 SBNN/OBNN 与 ANN 的差距。该机制通过配置项 dfs.ha.tail-edits.in-progress 控制,默认为 false 即不打开。

  3. JournalNode EditLog cache
    现在为 JournalNode 增加了 EditLog 的内存 cache,cache 大小可配置(dfs.journalnode.edit-cache-size.bytes,默认1M),同理,这也是为了让 JournalNode 能够尽可能快的响应 SBNN/OBNN 的 tail 请求。

  4. SBNN/OBNN tail EditLog 的指数退避机制
    默认情况下,SNBB/OBNN 以一个固定的间隔从 JN 集群 tail EditLog(现网配置为60s),这个太长。现在为了尽可能缩小 SBNN/OBNN 和 ANN 的差距,这个值设置为0,即不间断地拉取,如果在拉取过程中,SBNN/OBNN 发现目前没有任何可用的 EditLog(可能是集群目前没有写请求),那么它们会做一个指数退避,逐渐拉长下一次拉取的间隔,最长间隔可配置(dfs.ha.tail-edits.period.backoff-max,默认0,即关闭指数退避,推荐配置为10s)。

OBNN 合入低版本 Hadoop 预估

OBNN 的合入将会是一个非常复杂的工程,和 EC、RBF 相比,他有几个显著的不同:

  1. 不仅需要修改 NameNode 组件,同时还要大幅度修改 JournalNode、客户端等组件,工作量较大。
  2. 特别地,EC 客户端和 RBF 都有自己独立的工程,但 OBNN 没有,相反,它是深度嵌入现有的 NN 代码之中,耦合非常之紧,这大大增大了合并难度。

相关文章

网友评论

    本文标题:Observer NameNode 的读写一致性保证

    本文链接:https://www.haomeiwen.com/subject/ebohwltx.html