HDFS高可用实现笔记

Posted by Jason on Tuesday, May 8, 2018

TOC

最近在负责HDFS高可用迁移方案,看到了一篇IBM分享的文章,详细解析了HDFS高可用实现,通俗易懂,觉得非常不错,特此记录下学习笔记,并感谢作者分享!

HDFS高可用实现

ZookeeperFailerController启动两个组件HealthMonitor、ActiveStandbyElector; * HealthMonitor使用多线程,通过HAServiceProtocol RPC接口monitorHealth方法监控namenode的健康状态,更新monitor.State;monitor.State状态发生变更时,回调通知ZookeeperFailerController决定是否完成主备切换; * AciveStandbyElector,在zkfc决定主备切换时通过其来完成主备选举;ActiveStandbyElector与zookeeper协作完成选主流程后,通知zkfc进行namenode状态切换;

AcitveStandbyElector主备选举

  • 选举:通过zookeeper写一致特性和临时节点机制完成主备选举;多个namenode尝试在zookeeper中创建一个路径为/hadoop-ha/${dfs.nameservices}/ActiveStandbyElector临时节点,写一致保证只有一个会创建成功,成功创建节点的nameNode被选举为主节点;
  • Watcher事件:ActiveStandbyElector向zookeeper注册watcher监控临时节点的状态,主要关注临时节点的删除事件(在namenode节点状态异常情况下,zkfc会删除临时节点;namenode主机宕机,该临时节点同样会自动删除)
  • 脑裂:ActiveStandbyElector由于某些原因(例如:jvm Full GC)会处于假死状态,这个时候与zookeeper之间session有可能超时,zookeeper会剔除ActiveStandbyElector客户端,进行重新选举;但是这时namenode仍然认为自己为Active状态,这时候就存在了两个主节点同时对外提供服务;
    • hdfs解决方案是:在选主成功后,会同时在zookeeper中创建持久节点/hadoop-ha/${dfs.nameservices}/ActiveBreadCrumb(持久节点不会因为主节点挂掉而自动删除);如果被选主节点发现前主节点遗留的持久节点,说明前主非正常退出,会回调zkfc的fencing接口,对旧的namenode节点进行fencing;
      • fencing操作,zkfc首先尝试调用旧的节点的HAServiceProtocol RPC 接口的 transitionToStandby 方法,看能不能把它转换为 Standby 状态;
      • 然后,如果 transitionToStandby 方法调用失败,那么就执行 Hadoop 配置文件之中预定义的隔离措施,Hadoop 目前主要提供两种隔离措施(shellfence、sshfence),通常会选择 sshfence

zkfc实现

  • HealthMonitor,zkfc关注其状态,非SERVICE_HEALTH或HAServiceStatus与monitor.state状态不符会删除zookeeper临时节点,发起选举;
  • ActiveStandbyElector,对选举结果做相应处理
    • 选主成功,将namenode变为Active状态;
      • 如果发现前主遗留的/hadoop-ha/${dfs.nameservices}/ActiveBreadCrumb节点,进行fencing操作,fencing操作成功后,才会将当namenode激活为Active状态;
    • 选主失败,将namenode变为Standby状态;

NameNode共享存储

NameNode 在执行 HDFS 客户端提交的创建文件或者移动文件这样的写操作的时候,会首先把这些操作记录在 EditLog 文件之中,然后再更新内存中的文件系统镜像。内存中的文件系统镜像用于 NameNode 向客户端提供读服务,而 EditLog 仅仅只是在数据恢复的时候起作用。

//NameNode 会定期对内存中的文件系统镜像进行 checkpoint 操作,在磁盘上生成 FSImage 文件
//fsimage_${end_txid},其中${end_txid} 表示这个 fsimage 文件的结束事务 id
|--- current
|  |--- VERSION
|  |--- edits_0000000000000001-000000000000019
|  |--- edits_inprogress_0000000000000020
|  |--- fsimage_00000000000000.md5
|  |--- fsimage_00000000000000
|  |--- fsimage_00000000000019.md5
|  |--- fsimage_00000000000019
|  |--- seen_txid
|--- in_use.lock

在 NameNode 启动的时候会进行数据恢复,首先把 FSImage 文件加载到内存中形成文件系统镜像,然后再把 EditLog 之中 FsImage 的结束事务 id 之后的 EditLog 回放到这个文件系统镜像上。

数据同步

Active NameNode每次修改前,通过JournalSet将EditLog同时写入本地磁盘和journalNode节点,在这里使用了Paxos的“大多数”思想,在大多数journalNode响应成功后,本次写入操作顺利完成。如果,数无法形成大多数,那么就认为提交 EditLog 失败,NameNode 停止服务退出进程。在这里可以看出,HDFS对 C(consistency,一致性) 和 A(availability,可用性) 进行了折衷,但还是可以认为 NameNode 选择了 C 而放弃了 A,这也符合 NameNode 对数据一致性的要求。

Standby NameNode定时通过http接口从journal Node同步Edit Log,同步的Edit Log都是处于finalized状态的segment,因为,处于 in-progress 状态的 Edit Log 当前正在被写入,被认为是处于不稳定的中间态,有可能会在后续的过程之中发生修改,比如被截断。Active NameNode 在完成一个 EditLog Segment 的写入之后,就会向 JournalNode 集群发送 finalizeLogSegment RPC 请求,将完成写入的 EditLog Segment finalized,然后开始下一个新的 EditLog Segment。一旦 finalizeLogSegment 方法在大多数的 JournalNode 上调用成功,表明这个 EditLog Segment 已经在大多数的 JournalNode 上达成一致。一个 EditLog Segment 处于 finalized 状态之后,可以保证它再也不会变化。

数据恢复

数据同步操作分为两步:首先要做的事情就是让 JournalNode 集群中各个节点上的 EditLog 恢复为一致;接下来要做的事情就是新的 Active NameNode 从 JournalNode 集群上补齐落后的 EditLog。

数据恢复一致
生成new epoch
  • 新选举的Active NameNode,首先广播获取所有JournalNode当前Epoch的最大值,然后生成new Epoch(最大值+1);
  • JournalNode得到最new Epoch后会比较是否大于本地存储的lastPromisedEpoch 。如果大,则返回它自己的本地磁盘上最新的一个 EditLogSegment 的起始事务 id;否则返回错误,Active NameNode得到大多数失败后,会结束同步过程,并退出NameNode,开始新一轮选主;
  • NameNode 收到大多数 JournalNode 对 newEpoch 的成功响应之后,就会认为生成新的 Epoch 成功;

Epoch会在每一次Edit Log同步时带上,用作Active NameNode的隔离操作。

数据对齐
  1. Active NameNode 向 JournalNode 集群发送 prepareRecovery RPC 请求,请求的参数就是选出的 EditLog Segment 的 id。JournalNode 收到请求后返回本地磁盘上这个 Segment 的起始事务 id、结束事务 id 和状态 (in-progress 或 finalized)。只要大多数的 JournalNode 的 prepareRecovery RPC 调用成功返回,NameNode 就认为成功。

  2. 选择进行同步的基准数据源。向 JournalNode 集群发送 acceptRecovery RPC 请求 NameNode 根据 prepareRecovery 的返回结果,选择一个 JournalNode 上的 EditLog Segment 作为同步的基准数据源。

    • 选择基准数据源的优先级是:finalized > in-progress > Epoch高的 > 事务id大的
  3. 在选定了同步的基准数据源之后,NameNode 向 JournalNode 集群发送 acceptRecovery RPC 请求,将选定的基准数据源作为参数。JournalNode 接收到 acceptRecovery RPC 请求之后,从基准数据源 JournalNode 的 JournalNodeHttpServer 上下载 EditLog Segment,将本地的 EditLog Segment 替换为下载的 EditLog Segment。只要大多数 JournalNode 的 acceptRecovery RPC 调用成功返回,NameNode 就认为成功。

  4. NameNode 向 JournalNode 集群发送 finalizeLogSegment RPC 请求,JournalNode 接收到请求之后,将对应的 EditLog Segment 从 in-progress 状态转换为 finalized。只要大多数 JournalNode 的 finalizeLogSegment RPC 调用成功返回,NameNode 就认为成功。

数据恢复

数据对齐操作结束后,可以保证 JournalNode 集群的大多数节点上的 EditLog 已经处于一致的状态,这样 NameNode 才能安全地从 JournalNode 集群上补齐落后的 EditLog 数据。

参考

  1. Hadoop NameNode 高可用 (High Availability) 实现解析

「真诚赞赏,手留余香」

Jason Blog

真诚赞赏,手留余香

使用微信扫描二维码完成支付


comments powered by Disqus