2019独角兽企业重金招聘Python工程师标准>>>
问题说明
NameNode是HDFS集群的单点故障点,每一个集群只有一个NameNode,如果这个机器或进程不可用,整个集群就无法使用。为解决这一问题提供了两种解决方法:NFS(采用网络共享文件模式)和QJM(HDFS使用Quorum Journal Manager来共享Action NameNode与Standby NameNode之间的edit logs)。
图 HDFS+Zookeeper实现高可用架构
整体架构
在一个HA集群中,会配置两个独立的NameNode。在任意时刻,只有一个NameNode作为活动的节点,另一个节点则处于备份状态。活动的NameNode负责执行所有修改命名空间以及删除备份数据块的操作,而备份的NameNode则执行同步操作,以保持与活动节点命名空间的一致性。
为了使备份节点与活动节点的状态能够同步一致,两个节点都需要同一组独立运行的节点(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):
- 共享存储隔离:同一时间只允许一个NameNode向JournalNodes写入editlog数据。
- 客户端隔离:同一时间只允许一个NameNode响应客户端的请求。
- Datanode隔离:同一时间只允许一个NameNode向DataNode下发名字节点指令,例如删除、复制数据块指令等等。
在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共享存储问题。
所有的HA实现方案都依赖于一个保存editlog的共享存储,这个存储必须是高可用的,并且能够被集群中所有的NameNode同时访问。Quorum Journal是一个基于paxos算法的HA设计方案。
Quorum Journal
Quorum Journal方案中有两个重要的组件。
- JournalNoe(JN):运行在N台独立的物理机器上,它将editlog文件保存在JournalNode的本地磁盘上,同时JournalNode还对外提供RPC接口QJournalProtocol以执行远程读写editlog文件的功能。
- Quorum Journal Manager(QJM):运行在NameNode上,(目前HA集群只有两个NameNode),通过调用RPC接口QJournalProtocol中的方法向JournalNode发送写入、排斥、同步editlog。
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方案有一下优点:
- JN进程可以运行在普通的PC上,而无需配置专业的共享存储硬件。
- 不需要单独实现fencing机制,Quorum Journal模式中内置了fencing功能。
- Quorum Journal不存在单点故障,集群中有2N+1个Journal,可以允许有N个Journal Node死亡。
- JN不会因为其中一个机器的延迟而影响整体的延迟,而且也不会因为JN数量的增多而影响性能(因为NameNode向JournalNode发送日志是并行的)
互斥机制
当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的目的。
editlog写流程
- 将editlog输出流中缓存的数据写入JN,对于集群中的每一个JN都存在一个独立的线程调用RPC 接口中的方法向JN写入数据。
- 当JN收到请求之后,JN会执行以下操作:
- 验证epoch number是否正确
- 确认写入数据对应的txid是否连续
- 将数据持久化到JN的本地磁盘
- 向QJM发送正确的响应
- QJM等待集群JN的响应,如果多数JN返回成功,则写操作成功;否则写操作失败,QJM会抛出异常。
- NameNode会调用FSEditlogLog下面的方法初始化editlog文件的输出流,然后使用输出流对象向editlog文件写入数据。
- 获取了QuorumOutputStream输出流对象之后,NameNode会调用write方法向editlog文件中写入数据,QuorumOutputStream的底层也调用了EditsDoubleBuffer双缓存区。数据回先写入其中一个缓冲区中,然后调用flush方法时,将缓冲区中的数据发送给JN。
读流程
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的操作。在切换之前,对于共享存储会执行以下操作:
- fencing原来的Active NameNode。这部分在互斥部分已经讲述。
- 恢复正在处理的editlog。由于NameNode发生了主从切换,集群中JN上正在执行写入操作的editlog数据可能不一致。例如,可能出现某些JN上的editlog正在写入,但是当前Active NameNode发生错误,这时该JN上的editlog文件就与已完成写入的JN不一致。在这种情况下,需要对JN上所有状态不一致的editlog文件执行恢复操作,将他们的数据同步一致,并且将editlog文件转化为FINALIZED状态。
- 当不一致的editlog文件完成恢复之后,这时原来的Standby NameNode就可以切换为Active NameNode并执行写editlog的操作。
- 写editlog。在前面已经介绍了。
日志恢复操作
日志恢复操作可以分为以下几个阶段:
- 确定需要执行恢复操作的editlog段落:在执行恢复操作之前,QJM会执行newEpoch()调用以产生新的epoch number,JN接收到这个请求后除了执行更新epoch number外,还会将该JN上保存的最新的editlog段落的txid返回。当集群中的大多数JN都发回了这个响应后,QJM就可以确定出集群中最新的一个正在处理editlog段落的txid,然后QJM就会对这个txid对应的editlog段落执行恢复操作了。
- 准备恢复:QJM向集群中的所有JN发送RPC请求,查询执行恢复操作的editlog段落文件在所有JN上的状态,这里的状态包括editlog文件是in-propress还是FINALIZED状态,以及editlog文件的长度。
- 接受恢复:QJM接收到JN发回的JN发回的响应后,会根据恢复算法选择执行恢复操作的源节点。然后QJM会发送RPC请求给每一个JM,这个请求会包含两部分信息:源editlog段落文件信息,以及供JN下载这个源editlog段落的url。
- 接收到这个RPC请求之后,JN会执行以下操作:
- 同步editlog段落文件,如果JN磁盘上的editlog段落文件与请求中的段落文件状态不同,则JN会从当前请求中的url上下载段落文件,并替换磁盘上的editlog段落文件。
- 持久化恢复元数据,JN会将执行恢复操作的editlog段落文件的状态、触发恢复操作的QJM的epoch number等信息(恢复的元数据信息)持久化到磁盘上。
- 当这些操作都执行成功后,JN会返回成功响应给QJM,如果集群中的大多数JN都返回了成功,则此次恢复操作执行成功。
- 完成editlog段落文件:到这步操作时,QJM 就能确定集群中大多数的JN保存的editlog文件的状态已经一致了,并且JN持久化了恢复信息。QJM就会向JN发送指令,将这个editlog段落文件的状态转化为FINALIZED状态,,并且JN会删除持久化的恢复元数据,因为磁盘上保存的editlog文件信息已经是正确的了,不需要保存恢复的元数据。
HDFS的高可用机制详解