一. 概念
所谓HA,即高可用(7*24小时不中断服务)
实现高可用最关键的是消除单点故障
hadoop-ha严格来说应该分成各个组件的HA机制——HDFS的HA、YARN的HA
二. 机制详解
1.保证高可用,为nameNode分配一个standBy的nameNode,客户端请求的还是ann,sbnn不接收客户端请求,ann和sbnn都接收datanode定时汇报的数据,由此可以减少主备切换的时间。当客户端发出请求,ann收到进行处理,将操作追加到edit日志,更新日志。
2.为了保证edit日志不至于在ann傻逼之后不可用,使用一个共享存储系统,在nn追加日志同时也将edit同步到这个共享存储系统之中,存储方案是Quorum Journal Manager,即JournalNode集群。该集群有n个节点,ann向jn集群同步edit数据,向每个节点都发送数据,成功与否遵循“多数原则”。sbnn从共享存储系统获取editLog数据,对ann上的数据进行共享。
JournalNode集群:基于zk实现,多数原则(cap原则,不保证数据完全一致性)为了让Standby Node与Active Node保持同步,这两个Node都与一组互相独立的进程保持通信(Journal Nodes)。当Active Node上更新了Namespace,它将记录修改日志发送给Journal Nodes。Standby noes将会从Journal Nodes中读取这些Edits。Standby Node将日志变更应用在自己的Namespace中。当Failover发生时,Standby将会在提升自己为Active之前,确保能够从JNS中读取所有的Edits。即在failover发生之前,Standy持有的namespace应该与Active保持完全同步。
4.ZKFailoverController(主备切换控制器,ZKFC):ZKFailoverController 作为独立的进程运行,对 NameNode 的主备切换进行总体控制。ZKFailoverController 能及时检测到 NameNode 的健康状况,在主 NameNode 故障时借助 Zookeeper 实现自动的主备选举和切换(当然 NameNode 目前也支持不依赖于 Zookeeper 的手动主备切换)
FC 最初的目的是为了实现 SNN 和 ANN 之间故障自动切换,FC 是独立与 NN 之外的故障切换控制器,ZKFC 作为 NameNode 机器上一个独立的进程启动 ,它启动的时候会创建 HealthMonitor 和 ActiveStandbyElector 这两个主要的内部组件,其中:
HealthMonitor:主要负责检测 NameNode 的健康状态,如果检测到 NameNode 的状态发生变化,会回调 ZKFailoverController 的相应方法进行自动的主备选举;
ActiveStandbyElector:主要负责完成自动的主备选举,内部封装了 Zookeeper 的处理逻辑,一旦 Zookeeper 主备选举完成,会回调 ZKFailoverController 的相应方法来进行 NameNode 的主备状态切换。
ActiveNameNode选举:ZKFC启动的时候会在Zookeeper创建选举节点(临时的ZNode),如果创建成功,ZKFC会告诉这个NameNode它就是active主节点。另外一个ZKFC创建失败,则与它绑定的NameNode就是standby备用节点,创建失败的那个ZKFC就监控这个临时节点的变化。(在zk上创建的节点其实相当于一个锁,谁获取到谁就是ann,另一个是sbnn,当ann出现问题,会通过删除zk上的临时节点的方式,释放锁,此时sbnn就可以获取到成为ann的资格)此外第一次启动的时候还会在Zookeeper上创建一个/hadoop-ha/{dfs.nameservices}/ActiveBreadCrumb的永久节点,这个永久节点保存着当前Active NameNode的信息,这么做是为了防止脑裂。
对本机NameNode的Health监控:初始化启动RPC服务,和nameNode建立服务连接。之后启动本机NameNode的Health监控,每个NameNode都会提供HAService服务,对应协议为org.apache.hadoop.ha.HAServiceProtocol。HealthMonitor作为消费方调用,来获取NameNode的健康状况。所有的操作触发都是根据NameNode的健康状况由HealthMonitor触发的,可以说HealthMonitor是整个ZKFC服务的引擎。HealthMonitor内部有一个守护线程MonitorDaemon,它负责执行NameNode的健康检查并触发相应的操作。
以上功能用来支持主备切换:
当ActiveNN出现问题,对其进行监控的ZKFC进程会删除zk上的临时节点,这样sbnn的ZKFC就会拿到ann失败的信息,然后获取active“锁”,通过rpc调用,通知sbnn成为ann。
为了防止脑裂
ps:何为脑裂?一个集群两个大脑
NameNode 在垃圾回收(GC)时,可能会在长时间内整个系统无响应,因此,也就无法向 zk 写入心跳信息,这样的话可能会导致临时节点掉线,备 NameNode 会切换到 Active 状态,这种情况,可能会导致整个集群会有同时有两个 NameNode,这就是脑裂问题。
当出现ZKFC所在JVM因为负载高或者Full GC时间长,这时候会导致Zookeeper客户端与Zookeeper服务端之间的心跳不正常,如果超过了session超时时间,Zookeeper服务端就会删除/hadoop-ha/{dfs.nameservices}/ActiveStandbyElectorLock这个临时节点,这个删除操作被立马被standby NameNode绑定的ZKFC感知到,然后创建/hadoop-ha/{dfs.nameservices}/ActiveStandbyElectorLock临时节点成功,最终成为新的active NameNode节点。这种情况下一定的时间内旧的active NameNode任然认为自己是active主节点。这时候整个HDFS系统存在两个active NameNode,产生了脑裂,这对强一致性的HDFS系统是不能容忍的。
如何解决脑裂:
脑裂问题的解决方案是隔离(Fencing),主要是在以下三处采用隔离措施:
第三方共享存储:任一时刻,只有一个 NN 可以写入;
DataNode:需要保证只有一个 NN 发出与管理数据副本有关的命令;
Client:需要保证同一时刻只有一个 NN 能够对 Client 的请求发出正确的响应。
关于这个问题目前解决方案的实现如下:
ActiveStandbyElector 为了实现 fencing,会在成功创建 Zookeeper 节点 hadoop-ha/${dfs.nameservices}/ActiveStandbyElectorLock 从而成为 Active NameNode 之后,创建另外一个路径为 /hadoop-ha/${dfs.nameservices}/ActiveBreadCrumb 的持久节点,这个节点里面保存了这个 Active NameNode 的地址信息;
Active NameNode 的 ActiveStandbyElector 在正常的状态下关闭 Zookeeper Session 的时候,会一起删除这个持久节点;
但如果 ActiveStandbyElector 在异常的状态下 Zookeeper Session 关闭 (比如前述的 Zookeeper 假死),假死导致 /hadoop-ha/${dfs.nameservices}/ActiveBreadCrumb 持久节点会保留下来,后面当另一个 NameNode 选主成功之后,会注意到上一个 Active NameNode 遗留下来的这个节点,从而会回调 ZKFailoverController 的方法对旧的 Active NameNode 进行 fencing。
在进行 fencing 的时候,会执行以下的操作:
a、首先尝试调用这个旧 Active NameNode 的 HAServiceProtocol服务的 transitionToStandby方法,看能不能把它转换为Standby状态。
b、如果 transitionToStandby 方法调用失败,那么就执行 Hadoop 配置文件之中预定义的隔离措施,Hadoop 目前主要提供两种隔离措施:
sshfence:通过 SSH 登录到目标机器上,执行命令 fuser 将对应的进程杀死;
shellfence:执行一个用户自定义的 shell 脚本来将对应的进程隔离;
只有在成功地执行完成 fencing 之后,选主成功的 ActiveStandbyElector 才会回调 ZKFailoverController 的 becomeActive 方法将对应的 NameNode 转换为 Active 状态,开始对外提供服务。
三.联邦机制
Federation 的核心思想是将一个大的 namespace 划分多个子 namespace,并且每个 namespace 分别由单独的 NameNode 负责,这些 NameNode 之间互相独立,不会影响,不需要做任何协调工作(其实跟拆集群有一些相似),集群的所有 DataNode 会被多个 NameNode 共享(在联邦机制中,所有nameNode和dataNode都是相同的clusterId,所以dataNode才会被所有nameNode共享)。
其中,每个子 namespace 和 DataNode 之间会由数据块管理层作为中介建立映射关系,数据块管理层由若干数据块池(Pool)构成,每个数据块(block)只会唯一属于某个固定的数据块池,而一个子 namespace 可以对应多个数据块池。每个 DataNode 需要向集群中所有的 NameNode 注册,且周期性地向所有 NameNode 发送心跳和块报告,并执行来自所有 NameNode 的命令。一个 block pool 由属于同一个 namespace 的数据块组成,每个 DataNode 可能会存储集群中所有 block pool 的数据块;
多个 NN 共用一个集群里的存储资源,每个 NN 都可以单独对外提供服务。
每个 NN 都会定义一个存储池,有单独的 id,每个 DN 都为所有存储池提供存储。
DN 会按照存储池 id 向其对应的 NN 汇报块信息,同时,DN 会向所有 NN 汇报本地存储可用资源情况
每个 block pool 内部自治,也就是说各自管理各自的 block,不会与其他 block pool 交流,如果一个 NameNode 挂掉了,不会影响其他 NameNode;
某个 NameNode 上的 namespace 和它对应的 block pool 一起被称为 namespace volume,它是管理的基本单位。当一个 NameNode/namespace 被删除后,其所有 DataNode 上对应的 block pool 也会被删除,当集群升级时,每个 namespace volume 可以作为一个基本单元进行升级。