NameNode存在单点故障问题。如果NN失效了,那么所有的客户端,包括MapReduce作业均无法读写文件,因为NN是唯一存储元数据与文件到数据块映射的地方。在这种情况下,Hadoop系统无法提供服务,为了减少由计算机硬件或软件易错性所带来的损失而导致NN节点失效问题,可以通过搭建HDFS高可用集群来实现NN的高可用。
HDFS高可用是配置了一对(active - standby)NN节点,当active NN 失效,standby NN就会接管它的任务并开始服务于来自客户端的请求,不会有任何明显的中断现象。
在这种情况下,要想从一个宕掉的NN中恢复,系统管理员需要启动一个拥有头文件系统元数据副本的新NN,并配置DataNode和客户端以便使用这个新的NN。新的NN必须满足以下情况才能响应服务:
在Hadoop2.0 中,HDFS NameNode 和 YARN ResourceManger(JobTracker 在 2.0 中已经被整合到 YARN ResourceManger 之中) 的单点问题都得到了解决,经过多个版本的迭代和发展,目前已经能用于生产环境。HDFS NameNode 和 YARN ResourceManger 的高可用 (High Availability,HA) 方案基本类似,两者也复用了部分代码,但是由于 HDFS NameNode 对于数据存储和数据一致性的要求比 YARN ResourceManger 高得多,所以 HDFS NameNode 的高可用实现更为复杂一些,下图为HDFS NN高可用架构图。
在高可用架构图中主要包含:NameNode、FailoverController、JournalNod、DataNode、Zookeeper,下面分别简要介绍每个节点信息:
主要包含Active NameNode 和 Standby NameNode,两台 NameNode 形成互备,一台处于 Active 状态,为主 NameNode,另外一台处于 Standby 状态,为备 NameNode,只有主 NameNode 才能对外提供读写服务。
ZKFailoverController又叫主备切换器,用于监控和控制NN的状态切换、及时检测NN的健康状况,当Active节点挂掉后,借助 Zookeeper 实现自动的主备选举和切换,将Standby NN状态切换为Active。
JN节点位于高可用集群的中间部分,即为绿色的小圆柱体,主要用于共享edits日志文件。因为edits文件一旦丢失,就会导致元数据丢失,数据随之也就丢失了,这次JN也和Zookeeper一样是集群的形式存在的,来保障edits不会丢失。
DataNode节点定时和NameNode节点进行通信(汇报数据块的位置信息),接受NN的指令,同时DataNode之间也会进行相互通信,执行数据块复制任务。
为主备切换器提供主备选举支持,选择其中一个备用的NameNode为active。在这里搭建HA时,可以没有Secondary NN节点,在搭建完全分布式时的Secondary NN合并edits文件的功能由Standby的NN代替了。
NameNode 主备切换主要由 ZKFailoverController、HealthMonitor 和 ActiveStandbyElector 这 3 个组件来协同实现:
在介绍HealthMonitor之前,先介绍以下HAServiceProtocol接口:
协议接口,提供高可用性相关原语来监视和故障转移服务。HA框架可以使用这个接口来管理服务
主要方法:
org.apache.hadoop.ha.HAServiceStatus getServiceStatus()
-----返回服务的当前状态
void monitorHealth()
-----监控服务的健康状况
void transitionToActive(org.apache.hadoop.ha.HAServiceProtocol.StateChangeRequestInfo reqInfo)
-----请求服务转换到活动状态
void transitionToStandby(org.apache.hadoop.ha.HAServiceProtocol.StateChangeRequestInfo reqInfo)
-----请求服务转换到备用状态
ZKFailoverController 在初始化的时候会创建 HealthMonitor,HealthMonitor 在内部会启动一个线程来循环调用 NameNode 的 HAServiceProtocol RPC 接口的方法来检测 NameNode 的状态,并将状态的变化通过回调的方式来通知 ZKFailoverController。
HealthMonitor 主要检测 NameNode 的两类状态,分别是 HealthMonitor.State 和 HAServiceStatus。
状态 | 说明 |
---|---|
INITIALIZING | HealthMonitor 在初始化过程中,还没有开始进行健康状况检测 |
SERVICE_HEALTHY | NameNode 状态正常 |
SERVICE_NOT_RESPONDING | 调用NameNode的monitorHealth方法调用无响应或响应超时 |
SERVICE_UNHEALTHY | NameNode 还在运行,但是 monitorHealth 方法返回状态不正常,磁盘存储资源不足 |
HEALTH_MONITOR_FAILED | HealthMonitor 自己在运行过程中发生了异常,不能继续检测 NameNode 的健康状况,会导致 ZKFailoverController 进程退出 |
状态 | 说明 |
---|---|
INITIALIZING | NameNode 在初始化过程中 |
ACTIVE | 当前 NameNode 为主 NameNode |
STANDBY | 当前 NameNode 为主 NameNode |
STOPPING | 当前 NameNode 为主 NameNode |
HAServiceStatus 在状态检测之中只是起辅助的作用,在HAServiceStatus发生变化时,HealthMonitor 也会回调 ZKFailoverController 的相应方法来进行处理,具体处理见后文 ZKFailoverController 部分所述。
Namenode(包括YARN ResourceManager) 的主备选举是通过 ActiveStandbyElector 来完成的,主要是利用了 Zookeeper 的写一致性和临时节点机制,具体的主备选举实现如下:
如果HealthMonitor检测到对应的NameNode的状态正常,那么表示这个 NameNode有资格参加Zookeeper的主备选举。如果目前还没有进行过主备选举的话,那么相应的 ActiveStandbyElector 就会发起一次主备选举,尝试在ZK上创建一个路径为/hadoop-ha/{dfs.nameservices}/ActiveStandbyElectorLock 的临时节点 (其中{dfs.nameservices} 为 Hadoop 的配置参数dfs.nameservices的值),Zookeeper 的写一致性会保证最终只会有一个 ActiveStandbyElector 创建成功,那么创建成功的 ActiveStandbyElector 对应的 NameNode 就会成为主ameNode,ActiveStandbyElector 会回调 ZKFailoverController 的方法进一步将对应的 NameNode 切换为 Active 状态。而创建失败的NameNode成为备NameNode,ActiveStandbyElector会回调ZKFailoverController的方法进一步将对应的NN切换为Standby状态。
不管创建/hadoop-ha/${dfs.nameservices}/ActiveStandbyElectorLock 节点是否成功,ActiveStandbyElector 随后都会向 Zookeeper 注册一个 Watcher 来监听这个节点的状态变化事件,ActiveStandbyElector 主要关注这个节点的 NodeDeleted 事件。
如果Active NameNode对应的HealthMonitor检测到NameNode的状态异常时,ZKFailoverController 会主动删除当前在Zookeeper上建立的临时节点/hadoop-ha/{dfs.nameservices}/ActiveStandbyElectorLock,这样处于Standby状态的NameNode的ActiveStandbyElector注册的监听器就会收到这个节点的NodeDeleted事件。收到这个事件之后,会马上再次进入到创建/hadoop-ha/{dfs.nameservices}/ActiveStandbyElectorLock 节点的流程,如果创建成功,这个本来处于Standby状态的NameNode就选举为主NameNode并随后开始切换为Active状态。
当然,如果是 Active 状态的NameNode所在的机器整个宕掉的话,那么根据 Zookeeper 的临时节点特性,/hadoop-ha/{dfs.nameservices}/ActiveStandbyElectorLock 节点会自动被删除,从而也会自动进行一次主备切换。
Zookeeper 在工程实践的过程中经常会发生的一个现象就是 Zookeeper 客户端“假死”,所谓的“假死”是指如果 Zookeeper 客户端机器负载过高或者正在进行JVM Full GC,那么可能会导致 Zookeeper 客户端到Zookeeper服务端的心跳不能正常发出,一旦这个时间持续较长,超过了配置的Zookeeper Session Timeout参数的话,Zookeeper 服务端就会认为客户端的session已经过期从而将客户端的Session关闭。“假死”有可能引起分布式系统常说的双主或脑裂 (brain-split) 现象。具体到本文所述的NameNode,假设 NameNode1 当前为 Active 状态,NameNode2 当前为 Standby 状态。如果某一时刻 NameNode1 对应的 ZKFailoverController 进程发生了“假死”现象,那么Zookeeper 服务端会认为NameNode1挂掉了,根据前面的主备切换逻辑,NameNode2会替代NameNode1进入Active状态。但是此时 NameNode1可能仍然处于Active状态正常运行,即使随后 NameNode1 对应的 ZKFailoverController 因为负载下降或者Full GC结束而恢复了正常,感知到自己和Zookeeper的Session已经关闭,但是由于网络的延迟以及 CPU 线程调度的不确定性,仍然有可能会在接下来的一段时间窗口内NameNode1认为自己还是处于 Active 状态。这样NameNode1和NameNode2都处于Active状态,都可以对外提供服务。这种情况对于 NameNode 这类对数据一致性要求非常高的系统来说是灾难性的,数据会发生错乱且无法恢复。Zookeeper 社区对这种问题的解决方法叫做 fencing,中文翻译为隔离,也就是想办法把旧的Active NameNode隔离起来,使它不能正常对外提供服务。
ActiveStandbyElector为了实现fencing,会在成功创建Zookeeper节点hadoop-ha/{dfs.nameservices}/ActiveStandbyElectorLock从而成为Active NameNode之后,创建另外一个路径为/hadoop-ha/{dfs.nameservices}/ActiveBreadCrumb 的持久节点,这个节点里面保存了这个Active NameNode的地址信息。Active NameNode的 ActiveStandbyElector 在正常的状态下关闭Zookeeper Session的时候 (注意由于/hadoop-ha/{dfs.nameservices}/ActiveStandbyElectorLock 是临时节点,也会随之删除),会一起删除节点/hadoop-ha/{dfs.nameservices}/ActiveBreadCrumb。但是如果 ActiveStandbyElector在异常的状态下Zookeeper Session关闭 (比如前述的 Zookeeper假死),那么由于/hadoop-ha/{dfs.nameservices}/ActiveBreadCrumb 是持久节点,会一直保留下来。后面当另一个NameNode选主成功之后,会注意到上一个Active NameNode遗留下来的这个节点,从而会回调 ZKFailoverController 的方法对旧的Active NameNode进行 fencing,具体处理见后文ZKFailoverController 部分所述。
ZKFailoverController在创建HealthMonitor和ActiveStandbyElector的同时,会向 HealthMonitor和 ActiveStandbyElector注册相应的回调函数,ZKFailoverController的处理逻辑主要靠 HealthMonitor 和 ActiveStandbyElector的回调函数来驱动。
如前所述,HealthMonitor 会检测 NameNode 的两类状态,HealthMonitor.State 在状态检测之中起主要的作用,ZKFailoverController 注册到 HealthMonitor 上的处理 HealthMonitor.State 状态变化的回调函数主要关注 SERVICE_HEALTHY、SERVICE_NOT_RESPONDING 和 SERVICE_UNHEALTHY 这 3 种状态:
而 HAServiceStatus 在状态检测之中仅起辅助的作用,在 HAServiceStatus 发生变化时,ZKFailoverController 注册到 HealthMonitor 上的处理 HAServiceStatus 状态变化的回调函数会判断 NameNode 返回的 HAServiceStatus 和 ZKFailoverController 所期望的是否一致,如果不一致的话,ZKFailoverController 也会调用 ActiveStandbyElector 的 quitElection 方法删除当前已经在 Zookeeper 上建立的临时节点退出主备选举。
在 ActiveStandbyElector 的主备选举状态发生变化时,会回调 ZKFailoverController 注册的回调函数来进行相应的处理:
sshfence:通过SSH登录到目标机器上,执行命令 fuser 将对应的进程杀死
shellfence:执行一个用户自定义的 shell 脚本来将对应的进程隔离
只有在成功地执行完成 fencing之后,选主成功的ActiveStandbyElector才会回调ZKFailoverController的 becomeActive方法将对应的NameNode转换为 Active 状态,开始对外提供服务。
截止今天为止,研究生入学第二周结束了,整个第二周的成果就当作第二周的周报吧,也算是心安,实在是无力肝下去了。。
下周目标: