NameNode是HDFS集群的单点故障点,每个集群只有一个NameNode,若是这个机器或进程不可用,整个集群就没法使用。为解决这一问题提供了两种解决方法:NFS(采用网络共享文件模式)和QJM(HDFS使用Quorum Journal Manager来共享Action NameNode与Standby NameNode之间的edit logs)。node
图 HDFS+Zookeeper实现高可用架构算法
在一个HA集群中,会配置两个独立的NameNode。在任意时刻,只有一个NameNode做为活动的节点,另外一个节点则处于备份状态。活动的NameNode负责执行全部修改命名空间以及删除备份数据块的操做,而备份的NameNode则执行同步操做,以保持与活动节点命名空间的一致性。 promise
为了使备份节点与活动节点的状态可以同步一致,两个节点都须要同一组独立运行的节点(JournalNodes,JNS)通讯。当Active NameNode执行了修改命名空间的操做时,它会按期将执行的操做记录保存在在editlog中,并写入JNS的多数节点中。而Standby NameNode会一直监听JNS上editlog的变化,若是发现editlog有改动,Standby NameNode就会读取editlog并与当前的命名空间合并。当发生了错误切换时,Standby节点会保证已经从JNS上读取了全部editlog并与命名空间合并,而后才会从Standby状态切换为Active状态。经过这种机制,保证了Active NameNode与Standby NameNode之间命名空间状态的一致性,也就是第一关系链的一致性。 缓存
为了使错误切换可以很快的执行完毕,就要保证Standby节点也保存了实时的block的存储信息,也就是第二关系链。这样发生错误切换时,Standby节点就不须要等待全部的数据节点进行全量数据块汇报,而直接能够切换到Active状态。为了实现这个机制,DataNode会同时向这两个NameNode发送心跳以及块汇报信息。这样就实现了Active NameNode 和standby NameNode 的元数据就彻底一致,一旦发生故障,就能够立刻切换,也就是热备。网络
这里须要注意的是 Standby NameNode只会更新数据块的存储信息,并不会向NameNode 发送复制或者删除数据块的指令,这些指令只能由Active NameNode发送。 架构
在HA架构中有一个很是重非要的问题,就是须要保证同一时刻只有一个处于Active状态的NameNode,不然机会出现两个NameNode同时修改命名空间的问题,也就是脑裂(Split-brain)。脑裂的HDFS集群极可能形成数据块的丢失,以及向DataNode下发错误的指令等异常状况。为了预防脑裂的状况,HDFS提供了三个级别的隔离机制(fencing): 分布式
在HA实现中还有一个很是重要的部分就是Active NameNode和Standby NameNode之间如何共享editlog日志文件。Active NameNode会将日志文件写到共享存储上。Standby NameNode会实时的从共享存储读取editlog文件,而后合并到Standby NameNode的命名空间中。这样一旦Active NameNode发生错误,Standby NameNode能够当即切换到Active状态。在Hadoop2.6中,提供了QJM(Quorum Journal Manager)方案来解决HA共享存储问题。oop
全部的HA实现方案都依赖于一个保存editlog的共享存储,这个存储必须是高可用的,而且可以被集群中全部的NameNode同时访问。Quorum Journal是一个基于paxos算法的HA设计方案。 性能
Quorum Journal方案中有两个重要的组件。 url
Quorum Journal方案依赖于这样一个概念:HDFS集群中有2N+1个JN存储editlog文件,这些editlog 文件是保存在JN的本地磁盘上的。每一个JN对QJM暴露QJM接口QJournalProtocol,容许NameNode读写editlog文件。当NameNode向共享存储写入editlog文件时,它会经过QJM向集群中全部的JN发送写editlog文件请求,当有一半以上的JN返回写操做成功时,即认为写成功。这个原理是基于Paxos算法的。
使用Quorum Journal实现的HA方案有一下优势:
当HA集群中发生NameNode异常切换时,须要在共享存储上fencing上一个活动的节点以保证该节点不能再向共享存储写入editlog。基于Quorum Journal模式的HA提供了epoch number来解决互斥问题,这个概念能够在分布式文件系统中找到。epoch number具备如下几个性质。
1.当一个NameNode变为活动状态时,会分配给他一个epoch number。
2.每一个epoch number都是惟一的,没有任意两个NameNode有相同的epoch number。
3.epoch number 定义了NameNode写editlog文件的顺序。对于任意两个NameNode ,拥有更大epoch number的NameNode被认为是活动节点。
当一个NameNode切换为活动状态时,它的QJM会向全部的JN发送命令,以获取该JN的最后一个promise epoch变量值。当QJM接受到了集群中多于一半的JN回复后,它会将所接收到的最大值加一,并保存到myepoch 中,以后QJM会将该值发送给全部的JN并提出更新请求。每一个JN会将该值与自身的epoch值相互比较,若是新的myepoch比较大,则JN更新,并返回更新成功;若是小,则返回更新失败。若是QJM接收到超过一半的JN返回成功,则设置它的epoch number为myepoch;不然它终止尝试为一个活动的NameNode,并抛出异常。
当活动的NameNode成功获取并更新了epoch number后,调用任何修改editlog的RPC请求都必须携带epoch number。当RPC请求到达JN后,JN会将请求者的epoch与自身保存的epoch相互对比,若请求者的epoch更大,JN就会更新本身的epoch,并执行相应的操做,若是请求者的epoch小,就会拒绝相应的请求。当集群中大多数的JN拒绝了请求时,此次操做就失败了。
当HDFS集群发生NameNode错误切换后,原来的standby NameNode将集群的epoch number加一后更新。这样原来的Active NameNode的epoch number确定小于这个值,当这个节点执行写editlog操做时,因为JN节点不接收epoch number小于自身的promise epoch的写请求,因此此次写请求会失败,也就达到了fencing的目的。
Standby NameNode会从JN读取editlog,而后与Sdtandby NameNode的命名空间合并,以保持和Active NameNode命名空间的同步。当Sdtandby NameNode从JN读取editlog时,它会首先发送RPC请求到集群中全部的JN上。JN接收到这个请求后会将JN本地存储上保存的全部FINALIZED状态的editlog段落文件信息返回,以后QJM会为全部JN返回的editlog段落文件构造输入流对象,并将这些输入流对象合并到一个新的输入流对象中,这样Standby namenode就能够从任一个JN读取每一个editlog段落了。若是其中一个JN失败了输入流对象会自动切换到另外一个保存了该edirlog段落的JN上。
当NameNode发生主从切换时,原来的Standby NameNode会接管共享存储并执行写editlog的操做。在切换以前,对于共享存储会执行如下操做:
日志恢复操做能够分为如下几个阶段: