转载:adoop HA高可用解析以及脑裂问题
Hadoop1.x:
Hadoop 的两大核心组件 HDFS 的NameNode 和 JobTracker 都存在着单点问题。
Hadoop2.x :
HDFS的NameNode 和 YARN的ResourceManger 的单点问题可以解决。
SecondaryNameNode保存的状态总是滞后于NameNode,所以这种方式难免会导致丢失部分数据,也可以解决。(NameNode和ResourceManger实现方式类似,前者更复杂)
下面主要讲HDFS NameNode的高可用(下图为HDFS NameNode 高可用架构图):
概念
Active NameNode 和 Standby NameNode:
两台 NameNode 形成互备,一台处于 Active 状态,为主 NameNode,另外一台处于 Standby 状态,为备 NameNode,只有主 NameNode 才能对外提供读写服务。
Active NN负责集群中所有客户端的操作;
Standby NN主要用于备用,它主要维持足够的状态,如果必要,可以提供快速
的故障恢复。
FailoverController(主备切换器):
FailoverController 作为独立的进程运行,对 NameNode 的主备切换进行总体控制。FailoverController 监测 NameNode 的健康状况,在主 NameNode 故障时借助 Zookeeper 实现自动的主备选举和切换。
HealthMonitor :
心跳监控(HealthMonitor)是一个周期性工作的后台线程,它在循环中周期性的同HA服务进心跳,负责跟踪NameNode服务的健康状况,并在健康状况变化时调用failover控制器的回调方法。
HealthMonitor 主要负责检测 NameNode 的健康状态,如果检测到 NameNode 的状态发生变化,会回调 ZKFC(ZKFailoverController) 的相应方法进行自动的主备选举。
ActiveStandbyElector
ActiveStandbyElector 主要负责完成自动的主备选举,内部封装了 Zookeeper 的处理逻辑,一旦 Zookeeper 主备选举完成,会回调 ZKFailoverController 的相应方法来进行 NameNode 的主备状态切换。
共享存储系统:
共享存储系统是实现 NameNode 的高可用最为关键的部分,共享存储系统保存了 NameNode 在运行过程中所产生的 HDFS 的元数据。主 NameNode 和备NameNode 通过共享存储系统实现元数据同步。在进行主备切换的时候,新的主 NameNode 在确认元数据完全同步之后才能继续对外提供服务。
Quorum-based 存储 + ZooKeeper:
备注:使用第三方的共享存储系统(NAS/SAN)部署复杂,易出错,无法有效解决脑裂问题,而且故障时需要手动转移
QJM(Quorum Journal Manager)是基于Paxos(基于消息传递的一致性算法)。Paxos算法是解决分布式环境中如何就某个值达成一致,基于消息传递的一致性算法,可用于状态的同步服务;
QJM(Quorum Journal Manager)是Hadoop专门为Namenode共享存储开发的组件。其集群运行一组Journal Node,每个Journal 节点暴露一个简单的RPC接口,允许Namenode读取和写入数据,数据存放在Journal节点的本地磁盘。当Namenode写入edit log时,它向集群的所有Journal Node发送写入请求,当多数节点回复确认成功写入之后,edit log就认为是成功写入。例如有3个Journal Node,Namenode如果收到来自2个节点的确认消息,则认为写入成功。
而在故障自动转移的处理上,引入了监控Namenode状态的ZookeeperFailController(ZKFC)。ZKFC一般运行在Namenode的宿主机器上,与Zookeeper集群协作完成故障的自动转移。整个集群架构图如下:
实现方式:
(1) 初始化后,Active把editlog日志写到2N+1上JN上,每个editlog有一个编号,每次写editlog只要其中大多数JN返回成功(即大于等于N+1)即认定写成功。
edit log数据保存在Journal Node本地磁盘,该路径在配置中使用dfs.journalnode.edits.dir属性指定。
(2) Standby定期从JN读取一批editlog,并应用到内存中的FsImage中。
(3) 如何fencing: NameNode每次写Editlog都需要传递一个编号Epoch给JN,JN会对比Epoch,如果比自己保存的Epoch大或相同,则可以写,JN更新自己的Epoch到最新,否则拒绝操作。在切换时,Standby转换为Active时,会把Epoch+1,这样就防止即使之前的NameNode向JN写日志,也会失败。
(4) 写日志:
- (a) NN通过RPC向N个JN异步写Editlog,当有N/2+1个写成功,则本次写成功。
- (b) 写失败的JN下次不再写,直到调用滚动日志操作,若此时JN恢复正常,则继续向其写日志。
- (c) 每条editlog都有一个编号txid,NN写日志要保证txid是连续的,JN在接收写日志时,会检查txid是否与上次连续,否则写失败。
(5) 读日志:
- (a) 定期遍历所有JN,获取未消化的editlog,按照txid排序。
- (b) 根据txid消化editlog。
(6) 切换时日志恢复机制
- (a) 主从切换时触发
- (b) 准备恢复(prepareRecovery),standby向JN发送RPC请求,获取txid信息,并对选出最好的JN。
- (c) 接受恢复(acceptRecovery),standby向JN发送RPC,JN之间同步Editlog日志。
- (d) Finalized日志。即关闭当前editlog输出流时或滚动日志时的操作。
- (e) Standby同步editlog到最新
(7) 如何选取最好的JN
- (a) 有Finalized的不用in-progress
- (b) 多个Finalized的需要判断txid是否相等
- (c) 没有Finalized的首先看谁的epoch更大
- (d) Epoch一样则选txid大的。
DataNode 节点:
DataNode 会同时向主 NameNode 和备 NameNode 上报数据块的位置信息。共享 HDFS 的元数据信息以及共享HDFS的数据块和DataNode之间的映射关系。
NameNode主备切换过程
NameNode 主备切换主要由 ZKFailoverController、HealthMonitor 和 ActiveStandbyElector 这 3 个组件来协同实现:
ZKFailoverController 作为 NameNode 机器上一个独立的进程启动 (在 hdfs 启动脚本之中的进程名为 zkfc),启动的时候会创建 HealthMonitor 和 ActiveStandbyElector 这两个主要的内部组件,ZKFailoverController 在创建 HealthMonitor 和 ActiveStandbyElector 的同时,也会向 HealthMonitor 和 ActiveStandbyElector 注册相应的回调方法。
NameNode 实现主备切换的流程如图所示,有以下几步:
NameNode 主备过程.png
HealthMonitor 初始化完成后会启动内部的线程来定时调用对应 NameNode 的 HAServiceProtocol RPC 接口的方法,对 NameNode 的健康状态进行检测。
HealthMonitor 如果检测到 NameNode 的健康状态发生变化,会回调 ZKFailoverController 注册的相应方法进行处理。
如果 ZKFailoverController 判断需要进行主备切换,会首先使用ActiveStandbyElector 来进行自动的主备选举。
ActiveStandbyElector 与 Zookeeper 进行交互完成自动的主备选举。
ActiveStandbyElector 在主备选举完成后,会回调 ZKFailoverController 的相应方法来通知当前的 NameNode 成为主 NameNode 或备 NameNode。
ZKFailoverController 调用对应 NameNode 的 HAServiceProtocol RPC 接口的方法将 NameNode 转换为 Active 状态或 Standby 状态。
脑裂(split-brain)
脑裂(split-brain),指在一个高可用(HA)系统中,当联系着的两个节点断开联系时,本来为一个整体的系统,分裂为两个独立节点,这时两个节点开始争抢共享资源,结果会导致系统混乱,数据损坏。
脑裂问题处理:
共享存储的fencing,确保只有一个NN能写成功。使用QJM实现fencing,下文叙述原理。
DataNode的fencing,确保只有一个NN能命令DN。HDFS-1972中详细描述了DN如何实现fencing。
HDFS-1972是对hdfs修改的某些bug或则增加的功能
(1) 每个NN改变状态的时候,向DN发送自己的状态和一个序列号。
(2) DN在运行过程中维护此序列号,当主备切换时,新的NN在返回DN心跳时会返回自己的active状态和一个更大的序列号。DN接收到这个返回是认为该NN为新的active。
(3) 如果这时原来的active(比如GC)恢复,返回给DN的心跳信息包含active状态和原来的序列号,这时DN就会拒绝这个NN的命令。
此外HDFS-1972中还解决了一些有可能导致误删除block的隐患,在failover后,active在DN汇报所有删除报告前不应该删除任何block。
客户端fencing,确保只有一个NN能响应客户端请求。让访问standby NN的客户端直接失败。在RPC层封装了一层,通过FailoverProxyProvider以重试的方式连接NN。通过若干次连接一个NN失败后尝试连接新的NN,对客户端的影响是重试的时候增加一定的延迟。客户端可以设置重试此时和时间。