目录
在前面的文章 Zookeeper(2)基础架构与内部原理解析(数据存储、watch机制、监听器、选举机制)中有详细介绍分布式协调框架Zookeeper的相关组件和原理,本文结合Zookeeper当中的监听器再来进一步介绍HDFS的HA方案。
一、ZooKeeper监听器
关于ZooKeeper监听器有三个重要的逻辑:
-
注册:客户端向ZooKeeper集群注册监听器
-
监听事件:监听器负责监听特定的事件
-
回调函数:当监听器监听到事件的发生后,调用注册监听器时定义的回调函数
类比举例:
为了便于理解,举例:旅客住店无房可住的情况
-
一哥们去酒店办理入住,但是被告知目前无空房
-
这哥们告诉客服:你给我记住了,帮我留意一下有没有空出的房间,如果有,及时通知我(类似注册监听器,监听特定事件)
-
将近12点,有房客退房,有空闲的房间(事件)
-
客服发现有空房(监听到事件)
-
及时通知这哥们
-
这哥们收到通知后,做一些事,比如马上从附近酒吧赶回酒店(调用回调函数)
二、HDFS HA原理
关键逻辑:
①监听器:注册、监听事件、回调函数
②共享存储:JournalNode
在Hadoop 1.x版本,HDFS集群的NameNode一直存在单点故障问题:
-
集群只存在一个NameNode节点,它维护了HDFS所有的元数据信息
-
当该节点所在服务器宕机或者服务不可用,整个HDFS集群处于不可用状态
Hadoop 2.x版本提出了高可用 (High Availability, HA) 解决方案,HDFS HA方案主要分两部分:
①元数据同步
②主备切换
1、元数据同步
在同一个HDFS集群,运行两个互为主备的NameNode节点。
一台为主Namenode节点,处于Active状态,一台为备NameNode节点,处于Standby状态。
其中只有Active NameNode对外提供读写服务,Standby NameNode会根据Active NameNode的状态变化,在必要时切换成Active状态。
JournalNode集群
-
在主备切换过程中,新的Active NameNode必须确保与原Active NamNode元数据同步完成,才能对外提供服务
-
所以用JournalNode集群作为共享存储系统;
-
当客户端对HDFS做操作,会在Active NameNode中edits.log文件中作日志记录,同时日志记录也会写入JournalNode集群;负责存储HDFS新产生的元数据
-
当有新数据写入JournalNode集群时,Standby NameNode能监听到此情况,将新数据同步过来
-
Active NameNode(写入)和Standby NameNode(读取)实现元数据同步
-
另外,所有datanode会向两个主备namenode做block report
2、主备切换
ZKFC涉及角色:
每个NameNode节点上各有一个ZKFC进程
ZKFC即ZKFailoverController,作为独立进程存在,负责控制NameNode的主备切换
ZKFC会监控NameNode的健康状况,当发现Active NameNode异常时,通过Zookeeper集群进行namenode主备选举,完成Active和Standby状态的切换
-
ZKFC在启动时,同时会初始化HealthMonitor和ActiveStandbyElector服务
-
ZKFC同时会向HealthMonitor和ActiveStandbyElector注册相应的回调方法(如上图的①回调、②回调)
-
HealthMonitor定时调用NameNode的HAServiceProtocol RPC接口(monitorHealth和getServiceStatus),监控NameNode的健康状态,并向ZKFC反馈
-
ActiveStandbyElector接收ZKFC的选举请求,通过Zookeeper自动完成namenode主备选举
选举完成后回调ZKFC的主备切换方法对NameNode进行Active和Standby状态的切换
主备选举过程:
-
启动两个NameNode、ZKFC
-
两个ZKFC通过各自ActiveStandbyElector发起NameNode的主备选举,这个过程利用Zookeeper的写一致性和临时节点机制实现
-
当发起一次主备选举时,ActiveStandbyElector会尝试在Zookeeper创建临时节点
/hadoop-ha/${dfs.nameservices}/ActiveStandbyElectorLock
,Zookeeper的写一致性保证最终只会有一个ActiveStandbyElector创建成功 -
ActiveStandbyElector从ZooKeeper获得选举结果
-
创建成功的 ActiveStandbyElector回调ZKFC的回调方法②,将对应的NameNode切换为Active NameNode状态
而创建失败的ActiveStandbyElector回调ZKFC的回调方法②,将对应的NameNode切换为Standby NameNode状态
-
不管是否选举成功,所有ActiveStandbyElector都会在临时节点ActiveStandbyElectorLock上注册一个Watcher监听器,来监听这个节点的状态变化事件
-
如果Active NameNode对应的HealthMonitor检测到NameNode状态异常时,通知对应ZKFC
-
ZKFC会调用 ActiveStandbyElector 方法,删除在Zookeeper上创建的临时节点ActiveStandbyElectorLock(或者ActvieStandbyElector与ZooKeeper的session断开,临时节点也会被删除,但有可能此时原Active NameNode仍然是active状态)
-
此时,Standby NameNode的ActiveStandbyElector注册的Watcher就会监听到此节点的 NodeDeleted事件。
-
收到这个事件后,此ActiveStandbyElector发起主备选举,成功创建临时节点ActiveStandbyElectorLock,如果创建成功,则Standby NameNode被选举为Active NameNode(过程同上)
如何防止脑裂?
脑裂
在分布式系统中双主现象又称为脑裂,由于Zookeeper的“假死”、长时间的垃圾回收或其它原因都可能导致双Active NameNode现象,此时两个NameNode都可以对外提供服务,无法保证数据一致性
隔离
对于生产环境,这种情况的出现是毁灭性的,必须通过自带的隔离(Fencing)机制预防此类情况
原理
ActiveStandbyElector成功创建ActiveStandbyElectorLock临时节点后,会创建另一个ActiveBreadCrumb持久节点
ActiveBreadCrumb持久节点保存了Active NameNode的地址信息
当Active NameNode在正常的状态下断开Zookeeper Session,会一并删除临时节点ActiveStandbyElectorLock、持久节点ActiveBreadCrumb
但是如果ActiveStandbyElector在异常的状态下关闭Zookeeper Session,那么持久节点ActiveBreadCrumb会保留下来(此时有可能由于active NameNode与ZooKeeper通信不畅导致,所以此NameNode还处于active状态)
当另一个NameNode要由standy变成active状态时,会发现上一个Active NameNode遗留下来的ActiveBreadCrumb节点,那么会回调ZKFailoverController的方法对旧的Active NameNode进行fencing
①首先ZKFC会尝试调用旧Active NameNode的HAServiceProtocol RPC接口的transitionToStandby方法,看能否将其状态切换为Standby
②如果transitionToStandby方法切换状态失败,那么就需要执行Hadoop自带的隔离措施,Hadoop目前主要提供两种隔离措施:
- sshfence:SSH to the Active NameNode and kill the process;
- shellfence:run an arbitrary shell command to fence the Active NameNode
③只有成功地fencing之后,选主成功的ActiveStandbyElector才会回调ZKFC的becomeActive方法transitionToActive将对应的NameNode切换为Active,开始对外提供服务