HDFS High Availability
1. 背景
在Hadoop 2.0.0 之前,namenode 一直是单节点运行,存在单点故障。若是在namenode 节点出现问题,则会导致整个hdfs 集群均不可用。直到namenode进程恢复,或是在另一备用节点上启动namenode进程。
HDFS 的高可用(high availability)提供了配置一主(active)一备(standby)namenode 的功能,用于在主节点故障(或是手动维护时),快速的进行failover,将业务切换到standby namenode。
2. 架构
在一个典型的HA集群中,会有两个独立的机器被配置为两个Namenode。在任何时刻,两者中仅会有一个处于active 状态,而另一个处于standby状态。Active Namenode 负责处理集群中所有客户端的请求,而StandBy 仅是作为 slave 存在,维护状态信息,以供failover使用。
为了使得StandBy节点的状态与Active节点同步,当前的实现方式是:需要两个节点均有访问同一个共享存储设备(如NFS)的权限(这个限制在之后的版本中可能会有部分宽松)。
在Active 节点上发生任意一个namespace的修改(如put操作,更新了namenode的元数据)时,对应的修改会被写入到存储在共享存储的 edits 日志中。StandBy 节点持续监控此目录下的文件,如果看到有edits 记录,则将操作应用的自己的namespace。在发生failover时,Standby被提升为active状态,在此之前它会确保它已经读了共享目录下所有的edits。这个是为了确保namespace的状态在failover发生时已经完全同步到了Standby节点。
为了提供一个快速的failover,StandBy 节点还需要知道句群中数据块的位置信息。为了实现此目的,所有DataNode上均会配置两个namenode的地址,并且发送block块信息及heartbeats 到这两个namenode。
在HA中很重要的一点是:每个时刻仅能有一个NameNode为active 状态。否则namespace 状况会迅速分裂成两部分,导致数据丢失风险。为了确保这个性质,以及防止“脑裂”的场景,管理员必须为共享存储配置至少一个 “fencing method”。在一个failover发生时,如果无法确认前一个Active node 已经放弃了它的Active 状态,fencing process会用于切断前一个Active 访问个共享edits文件的权限。这样可以防止前一个Active namenode 之后对namespace进行任何修改,使新Active namenode安全的过度到failover。
3. 硬件资源
为了部署一个HA集群,若是使用的是NFS作为共享文件存储,则需要准备以下两部分资源:
1. 两台配置基本一致的 NameNode机器
2. 共享存储:两台NameNode均能读写此存储。一般来说,会使用一个NFS,并挂载到每个NameNode上。
使用 NFS 时,文件的高可用以及fault tolerance 须由NFS组件提供。
若是使用的Quorum Journal Manager 管理的edits 日志,则需要准备以下两部分资源:
1. 两台配置基本一致的 NameNode机器
2. JournalNode 机器:即运行JournalNode进程的机器。
JournalNode进程是相对较轻量级的进程,所这些守护进程可以与namenode进程、yarn ResourceManager进程等运行在同一个机器上。需要注意的是:至少需要有三个JournalNode守护进程运行,对edit log 的修改必须写到大多数JournalNode中,这样才可以避免单节点故障导致的问题。当然也可以设置超过3个的JournalNode,不过最好是基数个JNs,这样可以增加系可容错次数。需要注意的是,若正在运行的有N个JNs,则系统可同时容忍最多 (N-1)/2 次故障,并继续正常提供服务。
这里需要注意的是,在 HA 模式下,StandBy NameNode也可以处理namespace state的checkpoint(合并edits与fsimage文件)。所以在HA集群中,并不需要运行Secondary NameNode、CheckpointNode、或是BackupNode。而且这么做也会导致报错。
4. 配置
为了配置 HA NameNodes,必须要在hdfs-site.xml 文件增加一些配置项。
1. dfs.nameservices
nameservice的逻辑名称,可任取。与非HA模式不同,HA模式下使用此属性用于标识整个集群的namespace。在emr中(三个主节点均是)配置如下:
<property>
<name>dfs.nameservices</name>
<value>ha-nn-uri</value>
</property>
通过Web UI 界面,我们看一下非 HA 模式与 HA模式的NameNode对比:
Fig.4.1 非HA模式
Fig.4.2 HA模式
从 Fig.4.1 与 Fig.4.2 可以看到,在 HA模式下,多了两个属性,分别是Namespace 以及 Namenode ID。其中Namespace 为集群的标识符,Namenode ID 为各个Namenode 节点的 id。
2. dfs.ha.namenodes.[nameservice ID]
配置每个nameservice 下的namenode节点信息。DataNodes 据此获取集群中NameNodes的信息。当前最多仅可配置两个节点。
例如:
<property>
<name>dfs.ha.namenodes.ha-nn-uri</name>
<value>nn1,nn2</value>
</property>
这里表示ha-nn-uri的namespace下有两个namenode,分别名为nn1,nn2。
3. dfs.namenode.rpc-address.[nameservice ID].[name node ID]
每个namenode监听的RPC服务地址。例如:
<property>
<name>dfs.namenode.rpc-address.ha-nn-uri.nn1</name>
<value>ip-xx.xx.xx.xx-.com:8020</value>
</property>
<property>
<name>dfs.namenode.rpc-address.ha-nn-uri.nn2</name>
<value> ip-xx.xx.xx.xx-.com:8020</value>
</property>
这里要列出集群中所有 namenode的RPC 服务地址。
4. dfs.namenode.http-address.[nameservice ID].[name node ID]
每个 namenode 监听的 http 服务地址。例如:
<property>
<name>dfs.namenode.http-address.ha-nn-uri.nn1</name>
<value> ip-xx.xx.xx.xx-.com:50070</value>
</property>
<property>
<name>dfs.namenode.http-address.ha-nn-uri.nn2</name>
<value> ip-xx.xx.xx.xx-.com:50070</value>
</property>
类似于 rpc-address,这里要列出集群中所有 namenode 的 http 地址。如果集群使用了安全配置,则同时还需要设置 https-address。
5. dfs.namenode.shared.edits.dir
共享存储目录的地址,若是使用的 JournalNode进程,则需要列出所有JNs的地址,但是url必须只配置一个(这里url指的是 /ha-nn-uri)。例如:
<property>
<name>dfs.namenode.shared.edits.dir</name>
<value>qjournal://ip-xx.xx.xx.xx:8485;ip-yy.yy.yy.yy:8485;ip-zz.zz.zz.zz:8485/ha-nn-uri</value>
</property>
6. dfs.client.failover.proxy.provider.[nameservice ID]
HDFS 客户端用于与Active NameNode交互的Java class。这个class被DFS Client用于决定哪个NameNode是当前Active的。Hadoop自带了两种实现方式:ConfiguredFailoverProxyProvider以及 RequestHedgingProxyProvider(对于第一个call,并发invoke所有namenodes,并判断哪个是active的,然后后续的requests会发送到此active namenode,直到下次failover发生),例如:
<property>
<name>dfs.client.failover.proxy.provider.ha-nn-uri</name>
<value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value>
</property>
7. dfs.ha.fencing.methods
一组脚本或是Java classes,用于在failover发生时隔离 Active NameNode。在HA中很重要的一点是:在任何时间点,仅能有一个active的namenode。所以在Failover发生后,standby namenode转换成active状态前,我们首先确保当前active namenode 要么处于Standby 状态,要么它的进程已被终止。为了实现此功能,我们必须配置至少一个 fencing method(隔离方法)。可以配置多个fencing脚本,按逗号隔开,并按顺序执行,直到其中某个执行成功。在Hadoop中提供了两种fencing方法:shell 与 sshfence。
1. sshfence:使用fuser用户ssh到active namenode,并kill掉namenode进程。这里由于是使用ssh的方式,所以必须要能够直接ssh到目标节点而不使用密码。对此,我们还要必须配置 dfs.ha.fencing.ssh.private-key-files 参数,提供以逗号隔开的ssh 私钥文件,例如:
<property>
<name>dfs.ha.fencing.methods</name>
<value>sshfence</value>
</property>
<property>
<name>dfs.ha.fencing.ssh.private-key-files</name>
<value>/home/exampleuser/.ssh/id_rsa</value>
</property>
(也可以选择非标准的用户名或端口登录,以及timeout的配置等,详情参考:
)
2. shell:使用 shell 脚本隔离active namenode。也就是在fencing时,执行此shell脚本,例如:
<property>
<name>dfs.ha.fencing.methods</name>
<value>shell(/path/to/my/script.sh arg1 arg2 ...)</value>
</property>
在 EMR 中,使用的是 shell 脚本模式:
<property>
<name>dfs.ha.fencing.methods</name>
<value>shell(/bin/true)</value>
</property>
8. fs.defaultFS
在core-site.xml 中,HA模式与非HA模式的另一个区别是hdfs的地址。默认在非HA模式下,我们会使用 hdfs://namenode-addres:8020 地址作为默认 hdfs 地址。而在HA模式下,提供的是nameservice地址,例如:
<property>
<!-- URI of NN. Fully qualified. No IP.-->
<name>fs.defaultFS</name>
<value>hdfs://ha-nn-uri</value>
</property>
9. dfs.journalnode.edits.dir
JournalNode存储自身状态以及其他JNs状态的目录,只配置一条路径。例如:
<property>
<name>dfs.journalnode.edits.dir</name>
<value>/mnt/journalnode</value>
</property>
查看两个namenode节点上的 /mnt/journalnode/ 路径下文件,可以看到此目录下的edits log 大小与命名完全一致。在VERSION文件下,storageType的值为JOURNAL_NODE。
5. 管理命令
在 namenode 节点,可以使用 hdfs haadmin 命令管理 HA 模式,主要有以下几种命令:
1.-getAllServiceState
返回所有namenode的状态。连接到所有配置的namenode并判断它们当前的状态。例如:
[hadoop@ip-10-0-2-245 ~]$ hdfs haadmin -getAllServiceState
ip-10-0-2-245.cn-north-1.compute.internal:8020 active
ip-10-0-2-150.cn-north-1.compute.internal:8020 standby
2.-getServiceState <serviceId>
获取某个namenode的状态。连接到单个namenode并判断它们状态,例如:
[hadoop@ip-10-0-2-245 ~]$ hdfs haadmin -getServiceState nn1
Active
3.-transitionToActive <serviceId> 以及-transitionToStandby <serviceId>
将某个特定的namenode节点的状态切换。此命令会让指定namenode 直接切换状态。但是并不会做任何 fencing的操作,一般不建议使用此命令,而是使用hdfs haadmin -failover
4.-failover [--forcefence] [--forceactive] <serviceId> <serviceId>
发起从第一个namenode 到第二个namenode的failover。如果第一个 namenode处于Standby状态,则此命令仅简单地将第二个的状态切换为Active状态。如果第一个nn(namenode)处于Active状态,则会尝试将它转换为Standby 状态。如果失败,则指定文件中的fencing method会被按顺序调用,直到成功。仅在以上过程后,second nn 会被转换成Active状态。如果没有fencing method 成功,则second nn 不会被转换为Active 状态,并返回报错。
6. 自动Failover
在 hdfs haadmin 管理命令中提供了手动failover 的命令。但是在手动模式下,Active NameNode在发生异常后,集群不会自动进行failover。下面介绍自动failover的配置。
自动failover的实现需要在HA HDFS 的部署中增加两个新组件:Zookeeper quorum 和ZKFailoverController(简称ZKFC)进程。
Apache ZooKeeper是一个高可用的服务,维护了集群里部分共享(与协调工作相关)数据,通知客户端这部分数据的变化,并监控客户端的failures。HDFS自动failover 的实现依赖于Zookeeper 的以下功能:
1. Failure detection:集群中每个Namenode都会在ZooKeeper中有个persistent session。如果机器故障,则它对应于Zookeeper中的session会过期,继而通知其他namenode,表示一个failover需要被触发了
2. Active NameNode election:Zookeeper 提供了一个简单的机制用于选出一个节点作为Active节点。如果当前active namenode 出现故障,另一个节点会在Zookeeper内获取一个特殊的锁,表示它应该成为下一个active
ZKFailoverController(ZKFC)是一个新的组件。它是一个Zookeeper 客户端,也可以监控并管理NameNode 的状态。每个运行namenode的机器也会运行一个ZKFC,它负责以下功能:
1. Health monitoring:ZKFC根据health-check 命令定期ping本地Namenode。只要namenode定期返回healthy 状态,ZKFC认为此节点是healthy的。如果节点crash,或是unhealthy,则health monitor会将它标为unhealthy
2. ZooKeeper session management:当本地NameNode是healthy的,ZKFC会在Zookeeper内保有一个session。如果本地是NameNode 是active,它同时也会保有一个特别的“lock”znode。如果session过期,则lock znode会被自动删除
3. ZooKeeper-based election:如果本地NameNode是healthy的,并且ZKFC没有其他节点在当前保有lock znode,它会尝试获取lock。如果成功,则它“赢得选举”,并负责执行failover,以让本地NameNode成为Active。Failover的过程类似于上面提到的手动failover的过程:首先,(若有必要)前一个active被隔离,然后本地local NameNode转换为active 状态。
7. 部署ZooKeeper
在常规部署中,ZooKeeper 进程被配置于运行在3 个或5个节点。ZooKeeper是一个轻量级的服务,需要的资源也较少,所以可以将它部署在NameNode和Standby Node。
如果zookeeper 集群down掉,则之后hdfs自动failover不会被触发,但是正常运行的HDFS仍可以继续无影响(不受zookeeper宕机影响)运行。在Zookeeper再次启动后,HDFS会重新连接上zookeeper,且不会有其他影响。
8. 配置自动failover
在配置自动failover之前,需要停止集群。暂时并不支持从一个手动failover 模式无缝切换到自动failover模式
配置自动failover需要添加以下配置参数在hdfs-site.xml 中:
<property>
<name>dfs.ha.automatic-failover.enabled</name>
<value>true</value>
</property>
Zookeeper 的地址,需要在core-site.xml 下给出:
<property>
<name>ha.zookeeper.quorum</name>
<value>ip-xx.xx.xx.xx:2181,ip-yy.yy.yy.yy:2181,ip-zz.zz.zz.zz:2181</value>
</property>
在配置好以上参数后,可以通过以下命令初始化 zookeeper里所必须的状态,在任一NameNode上执行即可:
Hdfs zkfc -formatZK
它会创建一个znode,自动failover系统会存储它的数据在此znode。
在初始化znode数据后,即可以启动hdfs集群,不过这里也同时需要在各个集群节点启动 zkfc 守护进程:
hadoop-daemon.sh --script hdfs start zkfc
在emr中为:sudo start hadoop-hdfs-zkfc
9. Failover测试
首先我们看一下当期Active与Standby节点情况:
> hdfs haadmin -getAllServiceState
ip1:8020 active
ip2:8020 standby
其中 ip1 对应的 namenode为nn1,ip2对应的namenode为nn2。
在Zookeeper znode中,我们也可以看到对应信息:
> get /hadoop-ha/ha-nn-uri/ActiveBreadCrumb
ha-nn-uri
nn1)ip-1 �>(�>
> get /hadoop-ha/ha-nn-uri/ActiveStandbyElectorLock
ha-nn-uri
nn1)ip-1 �>(�>
然在ip-1重启namenode:
> hdfs haadmin -getAllServiceState
ip-1:8020 standby
ip-2:8020 active
在Zookeeper 中,查看对应znode信息:
> get /hadoop-ha/ha-nn-uri/ActiveBreadCrumb
ha-nn-uri
nn2)ip-2 �>(�>
> get /hadoop-ha/ha-nn-uri/ActiveStandbyElectorLock
ha-nn-uri
nn2)ip-2 �>(�>
References: