聊聊HDFS的高可用架构
通过本文,你可以了解到如下内容:
①HDFS的(High Availability,HA)架构。
②HDFS-HA的实现原理;ZKFC三大组件,切主的完整过程。
③总结、思考优化切主速度
一、HDFS的高可用架构
为什要做HA呢?想象一下没有HA时,如果HDFS集群在正常运行的过程中,NameNode节点发生故障,比如机器断电、网络故障等等,那么集群中失去了文件系统的元数据信息,整个集群将会不可用,直到NameNode节点恢复正常。这种情况也就是俗称的单点故障问题。HA就是为了解决这个问题的。HDFS的HA有两个NameNode,一个Active状态,一个Standby状态。如下图所示:
Active NN处理客户端的读写请求,将文件系统变化的editlog写到共享存储中(一般是JournalNode上),Standby NN去共享存储拉editlog,然后perform,让Standby NN自己的内存中的FSimage与Active NN尽量保持同步。StandBy节点也接收DataNode的块汇报,这样在切主的时候就会很快,不用再去计算块存放的信息。
同时还引入了ZKFC进程,用来监控NameNode的健康状态,如果Active NN的状态异常,ZKFC会进行重新选主(选主的过程依赖了Zookeeper的一致性),必要时也会fence掉(通过配置项中传入的脚本彻底杀死进程)原来的Active NN,让Standby NN变成Active NN继续维持整个集群的正常运转。
二、HDFS的HA的实现原理
通过在bin/hdfs脚本中寻找到启动ZKFC进程时加载的主类,可知ZKFC进程是在org.apache.hadoop.hdfs.tools.DFSZKFailoverController#main方法中启动的。
ZKFailoverController内部运行着三个组件。
HealthMonitor、ActiveStandbyElector、ZKFCRpcServer
//监控NameNode的健康状态
private HealthMonitor healthMonitor;
//用于选主、Zookeeper节点变化的回调
private ActiveStandbyElector elector;
//用于处理HA Admin命令的Rpc Server
protected ZKFCRpcServer rpcServer;
一般地,NameNode进程和DFSZKFailoverController进程布署在同一台物理机器上。HealthMonitor, ZKFCRpcServer和ActiveStandbyElector在同一个JVM进程中(即DFSZKFailoverController进程), NameNode是一个独立的JVM进程。
本文不堆砌源码,只讲主干的实现思路,如果各位有优化的想法或者想了解更多细节,可以自行研读源码。
① DFSZKFailoverController进程启动时会进行ZK初始化、HealthMonitor初始化、ZKFCRpcServer服务的初始化和启动。
伪代码如下:
doRun() {
initZK();
initRPC();
initHM();
startRPC();
}
其中initZK()方法的作用是读取配置文件中关于ZK的配置,并据此构造出ActiveStandbyElector对象。创建ZK连接,并将WatcherWithClientRef对象传入作为ZK事件的回调处理对象。
在WatcherWithClientRef对象watcher的process方法中是回调函数:
委派给了ActiveStandbyElector对象的processWatchEvent方法,后面讲。
InitHM()方法是主要进行HealthMonitor初始化工作,从配置中读取一些HealthMonitor用到的参数,设置回调函数,然后启动HealthMonitor线程。
HealthMonitor线程会周期性的检查NameNode节点的存储edit log的目录容量是否充足(大于dfs.namenode.resource.du.reserved
这个配置项)。如果不充足会抛出HealthCheckFailedException,交由回调函数处理。回调函数中会触发选举相关的逻辑。
InitRPC()和startRPC()用于初始化和启动处理HA Admin命令的ZKFC RPC服务器,这个不是本文的重点。
② HealthMonitor线程周期性的检查NameNode健康状态,如果发现NameNode异常了,则调用注册的回调函数。回调函数中会异步地删除ZK节点/hadoop-ha/ActiveStandbyElectorLock。进而触发监听此节点事件的ZK客户端的Watcher回调。开始进行新一轮选主,哪个ZK客户端写/hadoop-ha/ActiveStandbyElectorLock节点成功了,相应节点上的NameNode就会被选为Active。
③ 被选为Active的节点,调用becomeActive方法尝试成为Active NameNode,如果失败则Sleep一段时间,让其他节点有机会成为Active。正常becomeActive方法里面的流程会调用到NameNodeRpcServer里面的transitionToActive,完成切主过程。
补充
理解判断资源是否可用的逻辑,离不开如下配置项:dfs.namenode.resource.check.interval
默认值是5000,单位是ms。dfs.namenode.resource.checked.volumes.minimum
默认值是1。dfs.namenode.resource.du.reserved
默认值是104857600,单位字节。(100MB)
NameNode resource checker运行的时间间隔由第一个配置项决定,checker会计算NameNode storage volumes的可用空间大于dfs.namenode.resource.du.reserved
配置值的个数,如果个数小于dfs.namenode.resource.checked.volumes.minimum
配置值,那么NameNode会进入安全模式。
三、一些思考
由于最近的工作没有专门针对ZKFC、或者切主速度去进行优化。所以这里我主要是写一些自己在学习HDFS HA架构和切主过程源码中的一些思考。
如果我们要设计一个分布式文件存储系统或者其他master-slave架构的分布式系统,可以借鉴HDFS的这种HA方式,借助一个监控进程监测Active节点的状态,发现异常则切主。
HDFS HA体系几个主要的环节:
①HealthMonitor周期性监控NameNode
②NameNode状态异常触发回调,进而通过ZK选主。
③ 确定好主节点后调用NameNode的transitionToActive 和transitionToStandby RPC来切主。
所以优化的地方就在这三处:
① 周期性监控NameNode资源是否可用的时间间隔是个trade-off,缩短牺牲性能,调大的话,如果NameNode出现问题,hdfs hang住的时间变长。
② 回调这里可以画时序图来确定各个zk链接的工作情况,感觉没什么可以优化的空间。
③ transitionToActive和transitionToStandby是NameNodeRpcServer中的rpc。这两个过程中有很多步骤要做,后续可以仔细梳理每一项工作,然后进行优化,比如切主后,新主计算目录的quota的过程并行化优化,通过抓火焰图,分析一些耗时的队列清理工作,然后分析这些队列清理工作怎么优化,能否安全推迟到切主成功之后执行等等。