基于QJM的HDFS HA总结

来源:互联网 发布:ccleaner清除电脑数据 编辑:程序博客网 时间:2024/05/21 09:29

1     HDFS HA架构

在一个典型的HDFS HA场景中,通常由两个NameNode组成,一个处于active状态,另一个处于standby状态。Active NameNode接受client的RPC请求并处理,同时写自己的Editlog和共享存储上的Editlog,接收DataNode的Block report, block location updates和heartbeat;Standby模式的NameNode同样会接到来自DataNode的Block report、Block Location Updates和Heartbeat,同时会从共享存储的Editlog上读取并执行这些log操作,使得自己的NameNode中的元数据数据是同步的。所以说Standby模式的NameNode是一个热备(Hot Standby NameNode),一旦切换成Active模式,马上就可以提供NameNode服务。

为了能够实时同步Active和Standby两个NameNode的元数据信息(实际上editlog),需提供一个共享存储系统,可以是NFS、QJM(Quorum Journal Manager)或者Bookeeper。Active NameNode将数据写入共享存储系统,而Standby监听该系统,一旦发现有新数据写入,则读取这些数据,并加载到自己内存中,以保证自己内存状态与Active NameNode保持基本一致,如此这般,在紧急情况下Standby便可快速切为Active NameNode。

HDFS使用ZKFailoverController进行自动恢复。

1.1     Quorum Journal Manager

在QJM方案中,主备NameNode之间通过一组JournalNode同步元数据信息,一条数据只要成功写入多数JournalNode即认为写入成功。通常配置奇数个(2N+1)个JournalNode,这样,只要N+1个写入成功就认为数据写入成功,此时最多容忍N-1个JournalNode挂掉,比如3个JournalNode时,最多允许1个JournalNode挂掉,5个JournalNode时,最多允许2个JournalNode挂掉。

1.2     ZKFailoverController

HDFS NameNode自动切换由以下两个组件构成:

Ø  Zookeeper实例(奇数个节点)

Ø  ZKFailoverController(简称“ZKFC”)

ZKFC是一个Zookeeper客户端,负责监控和管理NameNode的状态,每台运行NameNode的机器上也会运行一个ZKFC进程。

Ø  健康状况监控:ZKFC周期性地与本地的NameNode交互,执行一些健康状况监测命令。

Ø  Zookeeper session管理:如果本地NameNode是健康的,则会持有Zookeeper上一个znode,如果它是active的,会持有zookeeper的仅有的一个特殊znode,该znode类型为ephemeral。一旦namenode挂掉后,会自动消失。

Ø  基于zookeeper的选举:如果本地NameNode是活的,而没有其他namenode持有特殊的znode,ZKFC将尝试获取这个znode,一旦获取成功后,则认为它“赢得了选举”,进而隔离之前的active namenode,自己转换为新的active namenode。

1.3     NameNode主从切换

一个Standby NN由Standby变成Active时,需要执行的操作:

1) fencing原来Active NN的写

2) recover in-progress logs。原来Active NN写EditLog过程中发生了主从切换,那么处在不同JournalNode上的EditLog的数据可能不一致,需要把不同JournalNode上的EditLog同步一致,并且finalized。(这个过程类似于HDFS append中的recover lease的过程)

3) startLogSegment。不一致的EditLog都同步一致且finalized,那么原来的Standby NN正式行驶正常的Active NN的写日志功能

4) write edits

5) finalizeLogSegment

2     QJM设计

QJM通过读写多个存储节点达到高可用性,同时为了恢复由于异常造成的多节点数据不一致性,引入了数据一致性算法。QJM逻辑图如下:

QJM的基本原理就是用2N+1台JournalNode存储EditLog,每次写数据操作有大多数(>=N+1)返回成功时即认为该次写成功,保证数据高可用。当然这个算法所能容忍的是最多有N台机器挂掉,如果多于N台挂掉,这个算法就失效。

2.1     写日志机制

写操作由主节点来完成,当主节点调用flush操作,会调用RPC同时向N个JN服务异步写日志,有N/2+1个节点返回成功,本次写操作才算成功。

主节点会标记返回失败的JN节点,下次写日志将不再写该节点,直到下次调用滚动日志操作,如果此时该JN节点恢复正常,之后主节点会向其写日志。虽然该节点丢失部分日志,由于主节点写入了多份,因此相应的日志并没有丢失。

为了保证写入每个日志文件txid的连续性,主节点保证分配的txid是连续的,同时JN节点在接受写日志的时候,首先会检查txid是否跟上次写连续,如果不连续会向主节点报错,连续则写入日志文件。

2.2     读日志机制

相比写日志过程,读日志要相对简单一些。

图 1读取日志示意图

当从节点触发读日志的时候,会经历如下几个步骤:

1、选择日志文件,建立输入流

从节点遍历出所有还没有消化的日志文件,同时过滤inprocess状态的文件。对于 每个JN节点上的日志文件,均按照txid从小到大进行排序放入一个集合。每个JN节点在从节点端均对应这样一个集合。再将每个JN节点间相同的日志文件 进行归类为一组(组内日志会检查fisrtTxid是否相等,及其lastTxid是否相等);每个组之间再按照txid从小到大进行排序,这样方便从节 点按照txid顺序消化日志;同时也会判断每个组之间txid是否连续。

2、消化日志

准备好输入流以后,开始消化日志,从节点按照txid先后顺序从每个日志组里面消化日志。在每个日志组里面,首先会检查起始txid是否正确,如果正确,从节点先消化第一个日志文件,如果消化第一个日志文件失败则消化第二个日志文件,以此类推,如果日志组内文件遍历完还没有找到需要的日志,则该日志消化失败,消化每个日志的如果消化的上一个txid等于该日志文件的lastTxid,则该 日志文件消化结束。

2.3     日志恢复

在从节点切换为主节点的过程中,会进行最近的日志段状态检查,如果没有转换为finalized状态会将其转换为该状态,日志恢复就处于该过程当中。

图 2恢复日志finalized状态图

2.3.1  触发条件

跟其他一些数据恢复方案不同,QJM并非每次写日志文件出现异常均恢复,而是从切换为主的情况下进行最新的一个日志文件的数据一致性检查,然后决定是否触发数据修复流程,之所以这样实现我想有如下原因:

在开始一个新的edits文件前,HDFS会确保之前最新的日志文件已经由inprocess状态转化为finalized状态,同时QJM每次操作有N/2+1个节点返回成功才算成功,因此除了最新日志文件前,之前老的日志文件是finalized状态,且是高可用的;仅最新的日志文件可能由于主节点 服务异常或者QJM某个进程异常导致日志没有正常转换为finalized状态,因此在从切换为主的时候需要恢复处理;

2.3.2  恢复流程

在日志恢复的过程中,需要经历准备恢复(prepareRecovery),接受恢复(acceptRecovery)两个阶段。

2.3.2.1 prepareRecovery

该操作向JN端发送RPC请求,查询需要恢复的日志段文件是否存在,如果存在则判断日志段文件状态(inprocess或finalized),同时也会返回epoch编号,NameNode根据返回的查询信息通过修复算法选择修复的源节点,准备进行数据修复。

2.3.2.2 acceptRecovery

计算获得源节点后,NameNode会向JN端发送恢复操作,JN节点根据接收到的 RPC恢复请求,判断当前节点是否需要进行日志修复,如果需要进行修复,则通过doGet方式到源节点下载需要恢复的目标日志文件。下载过程中,先将下载 的文件放到临时目录(tmp)目录下,下载完成后进行md5校验,检查是否有数据丢失,数据检查通过再将下载的文件放置到工作目录(current)下,这样数据恢复完成。

在JN节点执行该方法中,有两个问题需要考虑:

1、如果在JN节点下载日志的文件时候,源节点连接不通,会抛异常,如果有多个JN节点可以作为源节点,在NameNode调用JN节点的acceptRecovery方法是,可以考虑返回URL数组而不是单个URL,这样一个URL不能连接还可以尝试连接另外的JN节点进行文件下载;

2、有可能某JN节点下载日志文件的时候,自己进程挂掉,在QJM中,有对该问题的处理方式;

开始接触的时候我担心是否可能有文件既在被从节点读取,又在恢复该日志文件,通过分析后发现不会有何种情况,因为从节点消化的日志均是finalized状态的文件而不是inprocess状态的文件。

2.3.3  恢复算法

在2.3.2.1节中提到,查询到每个JN相关信息返回后,会使用修复算法选择源节点进行日志数据的修复,此处补上修复算法的策略:

1、首先判断JN节点是否有指定的txid,如果某节点没有,则该节点不会作为源节点;

2、如果JN节点存在指定的txid,然后判断该文件是否为finalized状态,如果不同的JN节点,txid所在的文件既有finalized状态的文 件又有inprocess状态的文件,以finalized状态文件为候选源节点,当然finalized状态的文件之间还需要判断结束txid是否相 等,然后返回其中任意一个节点作为源节点;

JN

segment

Last txid

JN1

edits_101-150

150

JN2

edits_101-150

150

JN3

Edits_inprogres_ 101

145

3、如果节点间文件均是inprocess状态的文件,首先判断其epoch编号,如果epoch编号不一致,则以epoch编号大的作为候选源节点;如果epoch编号一致,则选择结束txid更大的作为源节点。

JN

segment

Last txid

lastWriterEpoch

JN1

Edits_inprogres_ 101

150

1

JN2

Edits_inprogres_ 101

150

1

JN3

Edits_inprogres_ 101

145

1

 

3                    ZKFC设计

3.1     OVERVIEW

3.1.1  背景

HDFS-1623及其相关的JIRA将NN推向了HA,但是Failover的过程需要administrator手工的进行。即便如此,这样的HA还是显著的减少计划内的宕机时间,如计划内的软硬件升级。然而HDFS-1623没有解决自动的Failover,自动Failover需要依靠失效检测和状态转移。

3.1.2  zk-based方法

ZKFC描述了采用zk来完成AutomaticFailover的方法,ZK本身作为一个HA的组件提供以下特性:

Ø  能够强一致性的保存小部分任何信息(znode)

Ø  能够维护当client失效时自动删除的znode(an ephemeral node)

Ø  能够异步的获取znode信息改变的通知(watchers)

基于以上特性,可将zk用于以下方式:

Ø  Failure detector

NN在zk中创建一个ephemeral node,那么一旦NN失效,zk将会在设定的timeout的时间内删除该ephemeral node。

Ø  Active node locator

将当前活动NN的信息写入到zk中,其他的任何节点就可以籍此获取当前活动的NN

Ø  Mutual exclusion of active state

考虑到zk的高可用性,我们可以依靠ZK来确保任何时候集群中只有唯一的活动NN

 

其他的HA方法与此大同小异,不同之处在于使用其他的方式来替换其中的一两个组件。例如:Linux-HA也能提供一些相同的"积木块"。但是zk最吸引人的在于,它能将所有的特性放在一个系统内解决,并且很多使用hadoop的地方已经部署了zk,如hbase。

3.2     设计

3.2.1  组件化设计

ZK-based的automatic Failover主要由三个组件组成:

Ø  HealthMonitor:用于监控NN是否unavailable或者处于unhealth状态

Ø  ActiveStandbyElector:用于监控NN在zk中的状态

Ø  ZKFailoverController:从HealthMonitor和ActiveStandbyElector中订阅事件并管理NN的状态,另外ZKFC还需要负责fencing。

现阶段,上述三个组件都在跑在一个JVM中,这个JVM与NN的JVM在同一个机器上。但是是两个独立的进程。一个典型的HA集群,有两个NN组成,每个NN都有自己的ZKFC进程。

3.2.2  HealthMonitor设计

HealthMonitor 由HADOOP-7788完成提交,它由一个loop循环的调用一个monitorHealthrpc来检视本地的NN的健康性。如果NN返回的状态信息发生变化,那么它将经由callback的方式向ZKFC发送message。 HealthMonitor具有以下状态:

Ø  INITIALIZING:HealthMonitor已经初始化好,但是仍未与NN进行联通

Ø  SERVICE NOT RESPONDING:rpc调用要么timeout,要么返回值未定义。

Ø  SERVICE HEALTHY:RPC调用返回成功

Ø  SERVICE UNHEALTHY:RPC放好事先已经定义好的失败类型

Ø  HEALTH MONITOR FAILED:HealthMonitor由于未捕获的异常导致失败。

3.2.3  ActiveStandbyElector设计

1.       ActiveStandbyElector (committedin HADOOP-7992 and improved in HADOOP-8163, HADOOP-8212)主要负责凭借ZK进行协调,和ZKFC主要进行以下两个方面的交互:

1)       joinElection()--通知ASE,本地的NN可以被选为活动NN

2)       quitElection()--通知ASE,本地的NN不能被选为活动NN

2.       一旦ZKFC调用了joinElection,那么ASE将试图获取ZK中的lock(an ephemeral znode,automatically deleted when ZKFC crash or lost connection),如果ASE成功的创建了该lock,那么它向ZKFC调用becomeActive()。否则调用 becameStandby()并且开始监控这个lock(其他NN创建的)

3.       如果当前lock-holder失败了,另一个监控在这个lock上的ZKFC将被触发,然后试图获取这个lock。如果成功,ASE将同样的调用becomeActive方法来通知ZKFC

4.       如果ZK的session过期,那么ASE将在本地NN上调用enterNeutralMode而不是调用becomeStandby。因为他没法知道是否 有另一个NN已经准备好接管了。这种情况下,将本地NN转移到Standby状态是由fencing机制来完成(详见下文)。

3.2.4  ZKFC设计

ZKFC本身非常简单,它运行以下进程:

1.       启动时,通知HealthMonitor去监控本地NN。然后使用配置好的ZK去初始化ASE。但是绝不能立即参加Election。

2.       当HealthMonitor的状态改变时,ZKFC相应的做出如下反应:

1)       SERVICE_HEALTHY---通知elector去join Election,如果还没有的话。

2)       HEALTH MONITOR FAILED---中断所有的ZKFC进程,因为ZKFC也没法工作了。

3)       INITIALIZING---这个情况一般是NN刚刚重启还没准备好进行服务。ZKFC会退出Election,并且通知fencing是没必要进行的。因为NN总是以Standby来开始。

4)       Other states---退出Election,如果当前是在Election状态。

3.       当ActiveStandbyElector发布一个改变的时候,ZKFC做出如下反应:

1)       becomeActive() - ZKFC将在本地NN上调用transitionToActive()。如果失败了,将退出Election然后sleep一段时间,重新进入Election。Sleep是为了让其他准备好了的NN也有机会成为ActiveNN。这种情况下退出Election并不会删除breadcrumb node {这是为了确保了无论谁成为了ActiveNN之后可以fencing这个NN,尽管这个情况的失败可能导致这个NN可能会进入partially-active状态。}

2)       becomeStandby() - 在本地NN上调用transitionToStandby() ,如果失败,另一个NN将毫不犹疑的将这个NN进行fencing。(详见fencing)

3)       enterNeutralMode() - 当前没有反应,因为目前的设计中,不会进入此状态。

4)       fenceOldActive(...) - 详见fencing

5)       notifyFatalError(...) - 中断ZKFC,因为已经没法正常工作了。

4.       所有的调用都是在ZKFC上进行同步,这样确保了串行化所有事件的顺序确保其逻辑的正确性。

3.2.5  Fencing设计

HADOOP-8163对ASE进行了增强,主要是通过增加了fencing的回调机制,详细如下:

1.       在获取了ActiveLock之后,通知本地NN成为了Active之前,检查breadcrumb znode的存在性

1)       面包屑Znode存在的话,调用fenceOldActive(data)从那个NN上传入data数据,如果成功了,删除面包屑Znode

2)       如果fencing失败,log一个error,扔掉lock,sleep一会,重新进行Election。这样也给其他NN有机会成为ActiveNN

3)       使用本地NN的标识数据,创建一个新的breadcrumbnode。

2.       当退出Election的时候,quiting的NN能够自己判定是否需要fencing。如果需要,将删除breadcrumb node,然后关闭ZK session。

3.2.6  ZKFC的状态机图

 

3.2.7  手动Failover

尽管有了Automatic-Failover,但是能够进行手工的Failover对管理员来说也是个很好的选择,特别是在有计划的升级软硬件的时候和想要指定NN为Active的时候。

1.       开始的时候,只能通过Failover只能通过fail来触发。所以要达到想要的结果只能手工模拟fail的情况,比如kill掉一个NN以便让另一个NN成为ActiveNN。这有如下缺点:

1)       没有对standby机器的预先检查机制,如果standby不是ready的话,这将导致service不可用。

2)       如果standby在成为Active的时候失败,将不能很快切回去,因为另一个NN已经killed

3)       考虑到之前的ActiveNN被un-gracefully失效,这将导致fencing的执行,有可能导致STONITH等其它fencing,这对于管理员只想做个小改动来说too heavy。

2.       因此通过以下的改变,带来了手动的failover:

为每个namespace加入一个dfs.ha.auto-failover.enabled配置项。如果为true,将导致以下行为:

1)       haadmin -failover命令不再直接对NN进行Rpc调用,而是采用以下描述的方式来代替

2)       NN进入到一个只能接受mutative HAServiceProtocolRPCs从ZKFCs

3)       start-dfs.sh也可以自动在所有的NN机器上启动ZKFC

3.       手动Failover的操作

当管理员执行Failover时候,首先执行haadmin-failover -to <namenode>,我们会自动将其转化为no-automatic的设置语法 haadmin -failover -from<namenode> -to <namenode>。以下操作将被执行

1)       检查被选NN的健康状况,如果不是healthy,将抛出异常

2)       检查是否已经成为Active,如果是,返回成功。

3)       发送一个concedeLock() RPC给当前的活动ZKFC,这个ZKFC将会执行:

a)       发送一个transitionToStandby() RPC给本地NN

b)       如果成功,那么删除其留下的breadcrumb node,否则不动。

c)       退出Election,设置一个定时器,在下一个5-10秒内将不会再次参加Election。

4)       当这个RPC返回时,希望lock已经被其他NN给drop了,等待5-10秒在验证这个NN是否已经成功获取了该lock。等到本地NN要么成功成为了ActiveNN,要么失效,然后返回给client这个结果。

目前仅仅假定只有两个NN在参加Election,将来我们将支持多个standbyNN,这需要添加新的znode来标识需要成为Active的NN,然后更改Election的过程,在这个NN想获取lock之前先检查这个NN。

3.3     示例场景

3.3.1  ActiveNN产生JVM crash

一旦这种情况发生,HealthMonitor在调 用monitorHealth()将失效。然后HM将向ZKFC调用enterState(SERVICE_NOT_RESPONDING),本地ZKFC将退出Election,另外一个ZKFC获取active lock,执行fencing,变成active。

3.3.2  ActiveNN JVM freeze(e.gsigstop)

如果JVM freeze了但是没有crash掉,这与上面情况一直,monitorHealth会由于timeout而引发上述过程。FUTURE-WORK:使用JVMTI来判断NN是否在进行gc,如此可以使用另一个timeout来为gc进行failover。

3.3.3  ActiveNN machine crash

当整个机器crash了,ASE在zk的session将会过期,另一个ZKFC将会获取这个事件,引发failover。

3.3.4  Active ZKFC crash

尽管ZKFC设计简单,但是仍然有可能会crash掉,在这个情况下,failover将会被错误的触发。另一个NN的ZKFC将会对ActiveNN调用transitionToStandby让它放弃 active lock,然后进行aggressive fencing。尽管会成功,但是会导致进行了一次没有必要的failover。

3.3.5  Zookeeper crash

Ø  当zk集群crash了,那么所有的ZKFC将收到DISCONNECTED事件。然后ZKFC在本地NN调用enterNeutralMode,除此之外不做任何改变。系统除了不能执行failover之外,与其他情况无异。

Ø  当zk恢复了,clients立马能够重连。而zk能够将之前的session信息重新被各个client进行获取(在启动的一个timeou时间内)。所以所有的nodes将会重新获取session,不需要进行无必要的failover。

Ø  FutureWork:breadcrumb znode在这个情况下可以优先的给予到ActiveNN,在ZK挂掉之前。

3.4     待发掘的细节

Ø  和手动failover的集成

如前所述,尽管有了强大的automaticfailover,但是手动的failover在某些场合下仍是不二选择。

加 入一个简单的quiesceActiveState() RPC接口到ZKFC,这个rpc通知NN退出Election,并等待StandbyNN发起failover,如果等待超时仍未有failover发 起,那么这个NN重新获取Active lock。并向client汇报错误。

3.5     Future work

Ø  优先节点

某些情况,可能希望为将指定的NN成为ActiveNN。现阶段是通过公平竞争来获取Active lock从而变成ActiveNN。可以通过两个方式来达成这个目的:一个是延迟非优选节点加入Election。另一个是提供failback将非优选 节点从ActiveNN变成StandbyNN。

Ø  自我fencing

当HM通知本地NN变成unhealthy时候,在退出Election之前,ZKFC能够执行自我fencing。例如,它能进行fuse -k -9 <ipcport>来强制击杀本地NN。这个方法能够避免很多复杂的fencing机制

Ø  管理Process

当前的设计认为ZKFCprocess和本地NN独立的运行。NN挂掉了,ZKFC也不会视图去重启它,只是继续监控IPC端口直到NN被另外方式重启了。

当然如果ZKFC来负责NN的进程管理,这样使得部署要简单些,但是同时也增加了ZKFC本身的复杂度。因为java中的进程管理就是一坨屎。尽管如此当前的模块化设计足以使得以后如要进行这个工作很直接

 

参考资料

http://docs.hortonworks.com/HDPDocuments/HDP2/HDP-2.0.9.1/bk_system-admin-guide/content/ch_hadoop-ha.html

http://blog.cloudera.com/blog/2012/10/quorum-based-journaling-in-cdh4-1/

http://dongxicheng.org/hadoop-hdfs/hdfs-ha-federation-deploy/

http://www.binospace.com/index.php/hdfs-ha-quorum-journal-manager/

http://yanbohappy.sinaapp.com/?p=50

http://yanbohappy.sinaapp.com/?p=55

http://yanbohappy.sinaapp.com/?p=205

http://blog.csdn.net/liuhong1123/article/details/8881184

http://blog.csdn.net/chenpingbupt/article/details/7922042

https://issues.apache.org/jira/browse/HDFS-3042

https://issues.apache.org/jira/browse/HDFS-2185

https://issues.apache.org/jira/secure/attachment/12521279/zkfc-design.pdf

 

0 0
原创粉丝点击