一、简介

NameNode的HA可以个人认为简单分为共享editLog机制和ZKFC对NameNode状态的控制

二、一般导致NameNode切换的原因

随着集群规模的变大和任务量变多,NameNode的压力会越来越大,一些默认参数已经不能满足集群的日常需求,除此之外,异常的Job在短时间内创建和删除大量文件,引起NN节点频繁更新内存的数据结构从而导致RPC的处理时间变长,CallQueue里面的RpcCall堆积,甚至严重的情况下打满CallQueue,导致NameNode响应变慢,甚至无响应,ZKFC的HealthMonitor监控自己的NN异常时,则会断开与ZooKeeper的链接,从而释放锁,另外一个NN上的ZKFC进行抢锁进行Standby到Active状态的切换。这是一般引起的切换的流程。
当然,如果你是手动去切换这也是可以的,当Active主机出现异常时,有时候则需要在必要的时间内进行切换。

三、ZKFC的作用是什么?如何判断一个NN是否健康

在正常的情况下,ZKFC的HealthMonitor主要是监控NameNode主机上的磁盘还是否可用(空间),我们都知道,NameNode负责维护集群上的元数据信息,当磁盘不可用的时候,NN就该进行切换了。

  1. /**
  2. * Return true if disk space is available on at least one of the configured
  3. * redundant volumes, and all of the configured required volumes.
  4. *
  5. * @return True if the configured amount of disk space is available on at
  6. * least one redundant volume and all of the required volumes, false
  7. * otherwise.
  8. */
  9. public boolean hasAvailableDiskSpace() {
  10. return NameNodeResourcePolicy.areResourcesAvailable(volumes.values(),
  11. minimumRedundantVolumes);
  12. }

除了可用状态( SERVICE_HEALTHY )之外,还有 SERVICE_UNHEALTHY (磁盘空间不可用), SERVICE_NOT_RESPONDING (其他的一些情况)状态,在这两个状态中,它都认为NN是不健康的。

四、ZKFC是如何实现NameNode的自动切换?

当一个NameNode被成功切换为Active状态时,它会在ZK内部创建一个临时的znode,在znode中将会保留当前Active NameNode的一些信息,比如主机名等等。当Active NameNode出现失败或连接超时的情况下,监控程序会将ZK上对应的临时znode进行删除,znode的删除事件会主动触发到下一次的Active NamNode的选择。
因为ZK是具有高度一致性的,它能保证当前最多只能有一个节点能够成功创建znode,成为当前的Active Name。
现在查看zookeeper的znode信息:

  1. [root@node1 ~]# /etc/init.d/zookeeper-client
  2. Connecting to localhost:2181
  3. Welcome to ZooKeeper!
  4. JLine support is enabled
  5. WATCHER::
  6. WatchedEvent state:SyncConnected type:None path:null
  7. [zk: localhost:2181(CONNECTED) 0] ls /
  8. [cluster, controller_epoch, brokers, zookeeper, hadoop-ha, admin, isr_change_notification, consumers, config, hbase]
  9. [zk: localhost:2181(CONNECTED) 2] get /hadoop-ha
  10. cZxid = 0x100000002
  11. ctime = Tue Dec 05 19:43:07 EST 2017
  12. mZxid = 0x100000002
  13. mtime = Tue Dec 05 19:43:07 EST 2017
  14. pZxid = 0x700000017
  15. cversion = 3
  16. dataVersion = 0
  17. aclVersion = 0
  18. ephemeralOwner = 0x0
  19. dataLength = 0
  20. numChildren = 1
  21. [zk: localhost:2181(CONNECTED) 3] ls /hadoop-ha
  22. [myCluster]
  23. [zk: localhost:2181(CONNECTED) 4]

HDFS HA自动切换机制的核心对象是ZKFC,也就是我们平常在NameNode节点上会启动的ZKFC进程。
在ZKFC的进程内部,运行着3个对象服务:
HealthMonitor:监控NameNode是否不可用或是进入了一个不健康的状态。
ActiveStandbyElector:控制和监控ZK上的节点的状态。
ZKFailoverController:协调HealMonitor和ActiveStandbyElector对象,处理它们发来的event变化事件,完成自动切换的过程。
image.png
通常情况下Namenode和ZKFC同布署在同一台物理机器上, HealthMonitor, FailoverController, ActiveStandbyElector在同一个JVM进程中(即ZKFC), Namenode是一个单独的JVM进程。
image.png

(1) Monitor and try to take active lock: 向zookeeper抢锁,抢锁成功的zkfc,指导对应的NN成为active的NN; watch锁对应的znode,当前active NN的状态发生变化导致失锁时,及时抢锁,努力成为active NN
(2) Monitor NN liveness and health: 定期检查对应NN的状态, 当NN状态发生变化时,及时通过ZKFC做相应的处理
(3) Fences other NN when needed: 当前NN要成为active NN时,需要fence其它的NN,不能同时有多个active NN
ZKFC的线程模型总体上来讲比较简单的,它主要包括三类线程,一是主线程;一是HealthMonitor线程; 一是zookeeper客户端的线程。它们的主要工作方式是:
(1) 主线程在启动所有的服务后就开始循环等待
(2) HealthMonitor是一个单独的线程,它定期向NN发包,检查NN的健康状况
(3) 当NN的状态发生变化时,HealthMonitor线程会回调ZKFailoverController注册进来的回调函数,通知ZKFailoverController NN的状态发生了变化
(4) ZKFailoverController收到通知后,会调用ActiveStandbyElector的API,来管理在zookeeper上的结点的状态
(5) ActiveStandbyElector会调用zookeeper客户端API监控zookeeper上结点的状态,发生变化时,回调ZKFailoverController的回调函数,通知ZKFailoverController,做出相应的变化

  1. public enum State {
  2. /**
  3. * 1.The health monitor is still starting up.HealMonitor
  4. * 初始化启动状态
  5. */
  6. INITIALIZING,
  7. /**
  8. * 2.The service is not responding to health check RPCs.
  9. * 健康检查无响应状态
  10. */
  11. SERVICE_NOT_RESPONDING,
  12. /**
  13. * 3.The service is connected and healthy.
  14. * 服务检测健康状态
  15. */
  16. SERVICE_HEALTHY,
  17. /**
  18. * 4.The service is running but unhealthy.
  19. * 服务检查不健康状态
  20. */
  21. SERVICE_UNHEALTHY,
  22. /**
  23. * 5.The health monitor itself failed unrecoverably and can no longer provide accurate information.
  24. * 监控服务本身失败不可用状态
  25. * /
  26. HEALTH_MONITOR_FAILED;
  27. }

HealMonitor对象检测NameNode的健康状况的逻辑其实非常简单:发送一个RPC请求,查看是否有响应。

五、NameNode HA在源码中是如何实现的?

我们前面说到,ZKFC是如何判断NN是否健康,接下来当NN处于非健康状态时,NameNode是如何进行切换的呢?
在ZKFailoverController这个类中,实行了两个重要的Callbacks函数,一个叫ElectorCallbacks,另一个叫HealthCallbacks,顾名思义就是选举和健康检查用的回调函数,其中还有两个重要的组成部分 elector(ActiveStandbyElector) ,healthMonitor(HealthMonitor)。
ElectorCallbacks:

  1. /**
  2. * Callbacks from elector
  3. */
  4. class ElectorCallbacks implements ActiveStandbyElectorCallback {
  5. @Override
  6. public void becomeActive() throws ServiceFailedException {
  7. ZKFailoverController.this.becomeActive();
  8. }
  9. @Override
  10. public void becomeStandby() {
  11. ZKFailoverController.this.becomeStandby();
  12. }
  13. ...
  14. }

HealthCallbacks:

  1. /**
  2. * Callbacks from HealthMonitor
  3. */
  4. class HealthCallbacks implements HealthMonitor.Callback {
  5. @Override
  6. public void enteredState(HealthMonitor.State newState) {
  7. setLastHealthState(newState);
  8. recheckElectability();
  9. }
  10. }

对于HealthMonitor来说,在ZKFC进程启动的时候,就已经将HealthCallbacks注册进去了,HealthMonitor都会定期的检查NameNode是否健康,我们可以通过监控ha.health-monitor.check-interval.ms 去设置监控的间隔时间和通过参数 ha.health-monitor.rpc-timeout.ms 设置timeout时间, 当集群变大的时候,需要适当的设置改值,让ZKFC的HealthMonitor没那么“敏感” 。
ZKFC通过RPC调用监控NN进程,当出现异常时,则进入不同的处理逻辑,以下是简化的代码:

  1. private void doHealthChecks() throws InterruptedException {
  2. while (shouldRun) {
  3. try {
  4. status = proxy.getServiceStatus();
  5. proxy.monitorHealth();
  6. healthy = true;
  7. } catch (HealthCheckFailedException e) {
  8. ...
  9. enterState(State.SERVICE_UNHEALTHY);
  10. } catch (Throwable t) {
  11. ...
  12. enterState(State.SERVICE_NOT_RESPONDING);
  13. Thread.sleep(sleepAfterDisconnectMillis);
  14. return;
  15. }
  16. ...
  17. }

回调函数就是这么起作用啦,那么回调函数做了什么呢?总的来说,如果NN健康(SERVICE_HEALTHY )就加入选举,如果不健康就退出选举(SERVICE_UNHEALTHYSERVICE_NOT_RESPONDING

  1. case SERVICE_UNHEALTHY:
  2. case SERVICE_NOT_RESPONDING:
  3. LOG.info("Quitting master election for " + localTarget +
  4. " and marking that fencing is necessary");
  5. elector.quitElection(true);
  6. break;

说到退出选举就关系到 elector(ActiveStandbyElector) 了, true代表如果NN从Actice变为Standby出现异常是要去fence的 ,这就是为啥NN会挂掉的原因之一
如何退出选举?就是close zkClient的链接,让ZooKeeper上面的维持的选举锁消失

  1. void terminateConnection() {
  2. if (zkClient == null) {
  3. return;
  4. }
  5. LOG.debug("Terminating ZK connection for " + this);
  6. ZooKeeper tempZk = zkClient;
  7. ...
  8. try {
  9. tempZk.close();
  10. } catch(InterruptedException e) {
  11. LOG.warn(e);
  12. }
  13. ...
  14. }

对于ActiveStandbyElector来说,他有个WatcherWithClientRef类专门用来监听ZooKeeper上的的znode的事件变化,当事件变化时,就会调用ActiveStandbyElector的processWatchEvent的方法

  1. watcher = new WatcherWithClientRef();
  2. ZooKeeper zk = new ZooKeeper(zkHostPort, zkSessionTimeout, watcher);

  1. /**
  2. * Watcher implementation which keeps a reference around to the
  3. * original ZK connection, and passes it back along with any
  4. * events.
  5. */
  6. private final class WatcherWithClientRef implements Watcher {
  7. ...
  8. @Override
  9. public void process(WatchedEvent event) {
  10. hasReceivedEvent.countDown();
  11. try {
  12. hasSetZooKeeper.await(zkSessionTimeout, TimeUnit.MILLISECONDS);
  13. ActiveStandbyElector.this.processWatchEvent(
  14. zk, event);
  15. } catch (Throwable t) {
  16. fatalError(
  17. "Failed to process watcher event " + event + ": " +
  18. StringUtils.stringifyException(t));
  19. }
  20. }
  21. ...
  22. }

在ActiveStandbyElector的processWatchEvent方法中, 处理来自不同事件的逻辑 ,重新加入选举 或者 继续监控znode的变化 ,当另外一个ZKFC监控到事件变化得时候,就去抢锁,抢锁实质上就是创建znode的过程,而且创建的是CreateMode.EPHEMERAL类型 的,所以,当HealthMonitor监控到NN不健康时,就会断开连接,节点就会消失,watcher就会监控到NodeDeleted事件,进行创建节点。

  1. switch (eventType) {
  2. case NodeDeleted:
  3. if (state == State.ACTIVE) {
  4. enterNeutralMode();
  5. }
  6. joinElectionInternal();
  7. break;
  8. case NodeDataChanged:
  9. monitorActiveStatus();
  10. break;

又因为ActiveStandbyElector实现了StatCallback接口,当节点创建成功时,就会回调processResult方法看是否创建成功, 如果创建成功则去检查zkBreadCrumbPath是否存在之前的Active节点,如果存在,则调用RPC让其变为Standby,看能否转变成功,否则则SSH过去fence掉NN进程。 ,保持Active节点只有一个,并且恢复正常服务

六、NameNode因为断电导致不能切换的原理,怎样进行恢复

ActiveNN断电,网络异常,负载过高或者机器出现异常无法连接,Standby NN无法转化为Active,使得HA集群无法对外服务,原因是Active NN节点在断电和不能服务的情况下,zknode上保存着ActiveBreadCrumb, ActiveStandbyElectorLock两个Active NN的信息,ActiveStandbyElectorLock由于Active NN出现异常断开,Standby NN去抢锁的时候就会去检查ActiveBreadCrumb是否有上一次的Active NN节点,如果有,就会就会尝试让Active NN变为Standby NN,自己转化为Active NN,但是由于调用出现异常,所以会采用ssh的方式去Fence之前的Active NN,因为机器始终连接不上,所以无法确保old active NN变为Standby NN,自己也无法变为Active NN,所以还是保持Standby状态,避免出现脑裂问题。
解决方案是确定Active关机的情况下重新hdfs zkfc -formatZK就可以了。

七、总 结

NN GC或者在压力大的情况下可以调整GC算法和增加NameNode节点的线程数,加快NN对请求的处理速度,也可以分离节点的端口 dfs.namenode.rpc-address.ns1.nn2 和 dfs.namenode.servicerpc-address.ns1.nn2 分离client和datanode节点等服务类型的请求,进行分担压力,也可以适当的调整ZKFC的监控timeout的时间等等
但是遇到异常的job,只能通过别的方式去处理问题了